From 43e296649f8937e0aa3b638379644f657493a861 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Thu, 22 Oct 2026 16:41:51 +0530 Subject: [PATCH 01/10] feat(adapter-service): single and multi deployment modes on role network (#55, #66) Role network now reads networkAdapterDeploymentMode (single | multi, default single). In single it runs the consumer, network and provider tiers as four modules of one adapter (/, /publish, /_internal/network/, /_internal/provider/) on port 9201; in multi it is the network tier alone and provider and consumer run one release each. provider and consumer ignore the value; any other value fails the render. - No new role and no new namespace: both modes release network-adapter into namespace network-adapter, so switching modes upgrades it in place. - Single mode: config/network-adapter-config.yaml, routing generated at render time from config/routing-*.yaml with tier hops on localhost:/_internal/, one identity per tier from networkAdapterLayer.keys, and render guards (private keys, http.port vs targetPort, ingress path shape). - startupProbe, renamed liveness/readiness probe keys, PDB, topology spread and graceful shutdown settings; unused helpers removed. - examples/network-adapter.yaml (single) and examples/network.yaml (multi); CI values for both. - openbao-secrets: consumer-adapter-keys, provider-adapter-keys and provider-upstream also mirror into network-adapter for single mode. - docs/adr: ADR-0001 records the decision; ADR template and index. BREAKING: a role network release without networkAdapterDeploymentMode: multi becomes single on upgrade (the render fails until networkAdapterLayer.keys are set). The probes.* values keys are renamed. See charts/adapter-service/CHANGELOG.md (0.6.0). --- charts/adapter-service/CHANGELOG.md | 77 ++++ charts/adapter-service/Chart.yaml | 12 +- charts/adapter-service/README.md | 149 ++++++- charts/adapter-service/ci/lint-values.yaml | 1 + .../ci/network-adapter-values.yaml | 76 ++++ .../config/network-adapter-config.yaml | 381 ++++++++++++++++++ .../examples/network-adapter.yaml | 111 +++++ charts/adapter-service/examples/network.yaml | 6 +- charts/adapter-service/templates/NOTES.txt | 49 ++- charts/adapter-service/templates/_helpers.tpl | 57 +-- .../templates/_network-adapter.tpl | 103 +++++ .../adapter-service/templates/configmap.yaml | 37 ++ .../adapter-service/templates/deployment.yaml | 78 +++- charts/adapter-service/templates/ingress.yaml | 39 +- charts/adapter-service/values.yaml | 136 +++++-- charts/openbao-secrets/CHANGELOG.md | 8 + charts/openbao-secrets/Chart.yaml | 2 +- charts/openbao-secrets/values.yaml | 16 +- docs/adr/0000-template.md | 24 ++ ...-single-network-adapter-deployment-mode.md | 79 ++++ docs/adr/README.md | 12 + 21 files changed, 1337 insertions(+), 116 deletions(-) create mode 100644 charts/adapter-service/ci/network-adapter-values.yaml create mode 100644 charts/adapter-service/config/network-adapter-config.yaml create mode 100644 charts/adapter-service/examples/network-adapter.yaml create mode 100644 charts/adapter-service/templates/_network-adapter.tpl create mode 100644 docs/adr/0000-template.md create mode 100644 docs/adr/0001-single-network-adapter-deployment-mode.md create mode 100644 docs/adr/README.md diff --git a/charts/adapter-service/CHANGELOG.md b/charts/adapter-service/CHANGELOG.md index b8e36b07..5093db13 100644 --- a/charts/adapter-service/CHANGELOG.md +++ b/charts/adapter-service/CHANGELOG.md @@ -5,6 +5,83 @@ All notable changes to this chart are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this chart adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.6.0] - 2026-10-09 + +### Added +- **Two deployment modes, with single as the default.** + `networkAdapterDeploymentMode: single | multi` (default `single`), read by + role `network` only. In single, role `network` is the Network Adapter Layer: the consumer, network and provider tiers as four + modules of one process (`/` for the consumer, `/publish`, + `/_internal/network/`, `/_internal/provider/`), one release called `network-adapter`. In multi, + `provider`, `network` and `consumer` run one release per tier, unchanged. +- `config/network-adapter-config.yaml`: the layer's adapter config, mounted + like any role's file (an outbound connection pool and log level info on + every module). Each module is its tier's module, so a change to + a tier file that should apply to both deployment modes is made in both. Its routing + is generated at render time from `config/routing-*.yaml` + (`routing-network-adapter-{consumer,provider}.yaml`), with tier hosts rewritten + to `http://localhost:/_internal/`, so tier hops stay inside the pod. The render fails if `http.port` differs from `service.targetPort`. +- Three identities: each module signs as its own tier from + `networkAdapterLayer.keys.{consumer,network,provider}.existingSecret` + (`networkAdapterLayer.extraSubstitutions` for extra placeholders). A literal + private key in the config, including a `config:` override, fails the render. +- Flat paths: `/discover`, `/publish`, `/` and `/health`, the + same URLs as the multi deployment mode. The network and provider tiers are mounted + under `/_internal/`, which only the process itself calls. +- Ingress for the layer may expose only single-segment `/`, as literal + paths with `pathType` Prefix or Exact and no `use-regex` / `rewrite-target` + annotation; `/`, `/publish` and `/_internal*` are refused. `routing.config` fails the render for this + role. +- `examples/network-adapter.yaml` and `ci/network-adapter-values.yaml` + (3 replicas, PDB, spread, graceful shutdown). +- `topologySpreadConstraints`, + `terminationGracePeriodSeconds`, `preStopSleepSeconds`, `goMemLimit`; all off + by default. + +### Changed +- **Breaking:** `role: network` now defaults to the single deployment mode. + An existing network-tier release must set `networkAdapterDeploymentMode: + multi` (as `examples/network.yaml` does), or the upgrade turns it into the + Network Adapter Layer, which needs the three `networkAdapterLayer.keys` + Secrets. +- Docs, `values.yaml` and the example headers say single is the default + deployment mode and multi is optional. +- Naming: in single mode the release, Service, app name and namespace are + `network-adapter`, the same as the multi network tier's (telemetry service + `oan-network-adapter`, set in `config/network-adapter-config.yaml`). + Switching modes upgrades that release in place. + +### Fixed +- Liveness and readiness probes now render. Values used `probes.liveness` / + `probes.readiness`, which `common.probes` never read, so no adapter pod had a + readiness probe. Upgrading rolls every adapter pod once. +- The startup probe bounds first boot at 180s (`periodSeconds: 5` x + `failureThreshold: 36`) and holds off liveness until `/health` answers. + Pods that had no probe before now get a liveness probe, and the adapter + fetches `beckn.yaml` at boot, so without this a slow or proxied egress + would get a cold start killed after about 70s. +- NOTES.txt in single mode no longer prints `registry.url` and + `otel.*`: that role mounts `config/network-adapter-config.yaml` as is, so those + values never reached the pod and NOTES reported the opposite of what it ran. + The example and CI values no longer set them. +- The Ingress annotations merge tolerates `ingress.annotations: null`. + `ci/network-adapter-values.yaml` turns the Ingress on, so CI renders the path guard. + +### Removed +- Unused helpers `adapter-service.name`, `.appName`, `.otelServiceName`, + `.registryUrl` and `.otlpEndpoint` (nothing included them). +- **Breaking:** the `probes.liveness` and `probes.readiness` values keys, renamed to + `livenessProbe` / `readinessProbe`, plus a new `startupProbe`, on by + default. The liveness and readiness timings are unchanged. An environment + that overrides `probes.*` must rename those keys; the old keys never took + effect and are now silently ignored, which is why this is a minor bump + rather than a patch. + +### Unchanged +- Roles `provider`, `network`, `consumer`: same config, routing and manifests + apart from the probes (the chart version label and the config checksum + annotation also change with the version). + ## [0.5.2] - 2026-09-29 ### Changed diff --git a/charts/adapter-service/Chart.yaml b/charts/adapter-service/Chart.yaml index e3c0eb0d..b1d7bbb0 100644 --- a/charts/adapter-service/Chart.yaml +++ b/charts/adapter-service/Chart.yaml @@ -1,13 +1,17 @@ apiVersion: v2 name: adapter-service description: >- - One chart for the OAN Beckn adapters. provider, network and consumer are the same - image and config format, so `role` selects the handler role, the step list - and the routing target; install once per role. Stateless: an adapter's + One chart for the OAN Beckn adapters, in two deployment modes. single (the + default): the Network Adapter Layer (role network, networkAdapterDeploymentMode single) runs the + consumer, network and provider tiers as four modules of one process; + tier-to-tier calls stay inside the pod and each module keeps its own tier + identity. multi (optional): provider, network and consumer, one release per + tier. Same image and config format; role selects the handler role, the step + list and the routing target. Stateless: an adapter's identity is a keypair held in a Secret, and everything else it reads from the registry. type: application -version: 0.5.2 +version: 0.6.0 # The adapter binary, not this chart. `/health` reports the build it is # actually running, which is the number to trust over this one. appVersion: "v1.9.0" diff --git a/charts/adapter-service/README.md b/charts/adapter-service/README.md index 7bd5cc81..ac000a2a 100644 --- a/charts/adapter-service/README.md +++ b/charts/adapter-service/README.md @@ -1,8 +1,12 @@ # adapter-service -One chart for all three Beckn adapters. `provider`, `network` and `consumer` -are the same image and the same config format, so `role` selects the handler -role, the step list and where requests are routed. +One chart for the Beckn adapters, in two deployment modes. **single (the default):** +the Network Adapter Layer: role `network` runs all three tiers in one adapter. +**multi (optional):** roles `provider`, `network` and `consumer`, one release +per tier. `networkAdapterDeploymentMode: single | multi` (default `single`) +picks the mode; only role `network` reads it. All roles are the same image and the same +config format, so `role` selects the handler role, the step list +and where requests are routed. ``` consumer ──unsigned──▶ network ──▶ discovery @@ -12,11 +16,33 @@ role, the step list and where requests are routed. └──▶ mandi / agmarknet upstreams ``` +``` + role network, single mode (one adapter, one pod): + / ──lo──▶ /_internal/network/ ──▶ discovery + └──────lo────▶ /_internal/provider/ ──▶ upstream APIs + /publish ──lo──▶ /_internal/network/publish +``` + +The paths are flat and the same in both deployment modes: `/discover`, `/publish`, +`/` and `/health`. The network and provider tiers sit under +`/_internal/`, which only this process calls (over loopback). The ingress may +expose only single-segment `/` paths: `/publish` stays in-cluster for +the catalogue publisher, and `/_internal*` is never routed. + +Why flat: OAN is single-sided today -- our app calls out, and our own +discovery and upstreams answer. If OAN later accepts inbound calls from +external Beckn peers, a peer's `select` and our app's `select` are both +`POST /select`, and path alone cannot tell them apart. The distinguisher exists +(peer calls carry an `Authorization` signature, app calls do not), but ONIX +dispatches on path, so that would mean routing on signature presence at the +edge or giving inbound its own hostname. Neither changes these public paths. + | Role | Handler | Steps | Routes to | |---|---|---|---| | `provider` | `bpp` | `validateSign → addRoute → sign` | upstream APIs (several) | -| `network` | `bpp` | `validateSign → addRoute → sign` | discovery | +| `network` (multi) | `bpp` | `validateSign → addRoute → sign` | discovery | | `consumer` | `bap` | `addRoute → sign` | network | +| `network` (single, default) | `bap` + `bpp` (four modules) | each module runs its own tier's steps | discovery and the upstream APIs; tier hops stay inside the pod | `consumer` is the only one that accepts **unsigned** requests: the consumer app is inside the trust boundary, so there is no network signature to check. @@ -28,26 +54,46 @@ reads from the registry at request time. ## Install -Once per role. **Keep the release name per-role** — it is what the Service is -called, which is the DNS name the other adapters address. +Pick **one** deployment mode per environment (→ [Choosing a deployment mode](#choosing-a-deployment-mode-single-default-or-multi-optional)). +Install the Secrets first: the render fails without them. ```sh helm dependency build charts/adapter-service +``` + +**single (default): one release, the Network Adapter Layer** -helm upgrade --install provider-adapter charts/adapter-service -n oan \ - -f charts/adapter-service/examples/provider.yaml -helm upgrade --install network-adapter charts/adapter-service -n oan \ - -f charts/adapter-service/examples/network.yaml -helm upgrade --install consumer-adapter charts/adapter-service -n oan \ - -f charts/adapter-service/examples/consumer.yaml +```sh +helm upgrade --install network-adapter charts/adapter-service \ + -n network-adapter --create-namespace \ + -f charts/adapter-service/examples/network-adapter.yaml ``` -Each example sets `fullnameOverride` to `-adapter`. Without it the Service -would be named `-adapter-service`, and nothing routing by name would -resolve. Rename a release and you must update whatever routes to it — `consumer` -points at the network adapter, `network` points at discovery. +It needs `consumer-adapter-keys`, `network-adapter-keys`, +`provider-adapter-keys` and `provider-upstream` in `network-adapter` +(openbao-secrets mirrors them there). -The Secret comes first — the render fails without it. +**multi (optional): one release per tier, each in its own namespace** (see +[Multi mode](#multi-mode)). **Keep +the release and namespace names**: they make the Service DNS name the other +adapters address (the consumer routes to +`network-adapter.network-adapter.svc` and `provider-adapter.provider-adapter.svc`). + +```sh +helm upgrade --install provider-adapter charts/adapter-service \ + -n provider-adapter --create-namespace -f charts/adapter-service/examples/provider.yaml +helm upgrade --install network-adapter charts/adapter-service \ + -n network-adapter --create-namespace -f charts/adapter-service/examples/network.yaml +helm upgrade --install consumer-adapter charts/adapter-service \ + -n consumer-adapter --create-namespace -f charts/adapter-service/examples/consumer.yaml +``` + +Each example sets `fullnameOverride` to `-adapter` (`network-adapter` for the layer). Without it the Service +would be named `-adapter-service`, and nothing routing by name would +resolve. In multi, rename a release and you must update whatever routes to it — +`consumer` points at the network adapter, `network` points at discovery. In +single nothing routes to another release: the tiers call each other inside the +pod. ## What you must decide @@ -58,7 +104,7 @@ render error rather than a default: | Value | Why there is no default | |---|---| | `image.repository` | An empty one renders `ghcr.io/:tag`, which Helm and the API server both accept and which surfaces later as `ImagePullBackOff` | -| `keys.existingSecret.name` | Without an identity the pod starts, serves `/health`, reports Ready, and fails every signature it makes — in a peer's logs | +| `keys.existingSecret.name` | Without an identity the pod starts, serves `/health`, reports Ready, and fails every signature it makes — in a peer's logs. Role `network` in single mode uses `networkAdapterLayer.keys.{consumer,network,provider}.existingSecret` instead (three identities) | | `registry.url` | The adapter verifies every caller against the registry, so with none it can verify nobody | | `role` | It decides the handler role, the step list and the routing target. A default would give one adapter another one's behaviour | | `routing.rules` | An adapter with no route accepts requests and has nowhere to send them | @@ -121,12 +167,73 @@ the **public** half of the keypair. Missing, or carrying a different key, and every peer rejects what this adapter signs while the pod stays perfectly healthy. +## Choosing a deployment mode: single (default) or multi (optional) + +| Deployment mode | Releases → namespaces | Values | App calls | Catalogue publishes to | +|---|---|---|---|---| +| **single (default)**: Network Adapter Layer | `network-adapter` → `network-adapter` | `examples/network-adapter.yaml` | `network-adapter.network-adapter.svc:9201/` | `network-adapter.network-adapter.svc:9201/publish` | +| multi (optional) | `consumer-adapter`, `network-adapter`, `provider-adapter` → one namespace each, same name | `examples/{consumer,network,provider}.yaml` | `consumer-adapter.consumer-adapter.svc:9202/` | `provider-adapter.provider-adapter.svc:9200/publish` | + +**When to use which** + +| Use **single** (default) when | Use **multi** when | +|---|---| +| One team runs all three tiers in one cluster | The tiers are run by different parties or in different trust zones | +| You want fewer hops and fewer objects (one Deployment, Service, PDB, and an opt-in HPA) | A tier needs its own scaling or resource profile (e.g. a provider with heavy upstream calls) | +| | You need to roll out or roll back one tier without the others | + +Both sign as the same three identities from the same registry rows, so the +choice changes nothing in the registry. + +Role `network` in single mode runs all three tiers in one adapter, as four +modules of one process. Its config is `config/network-adapter-config.yaml` +(each module is its tier's module from `config/-config.yaml`, so a change +to a tier file that should apply to both deployment modes goes in both files). Its routing +is **generated** at render time from `config/routing-*.yaml`, so routes stay in +one place. Tier hops go to the pod's own listener, `localhost:` (any image). It signs as +the same three identities, from the same three Secrets, so the registry is the +same in both deployment modes. An environment's `config:` override for a multi role is NOT +seen by the Network Adapter Layer; give its release its own `config:` if +needed (it replaces `network-adapter-config.yaml` whole). + +Pick the deployment mode with ONE value, `networkAdapterDeploymentMode: single | +multi` (**default `single`**), on the `network-adapter` release. `single`: that +release is the Network Adapter Layer and nothing else is installed. `multi`: it +is the network tier, and `consumer-adapter` and `provider-adapter` are +installed beside it. `provider` and `consumer` ignore the value. Any other +value fails the render. + +Both modes release `network-adapter` into namespace `network-adapter`, so +switching modes is `helm upgrade` of that release with the other example, +plus installing or removing the other two releases. + +### Multi mode + +1. Set `networkAdapterDeploymentMode: multi` on the network release + (`examples/network.yaml` does). **An existing `role: network` release + without it becomes single on upgrade.** +2. Install the three releases as above. +3. In openbao-secrets, remove `network-adapter` from the namespaces of + `consumer-adapter-keys`, `provider-adapter-keys` and `provider-upstream`, so + the network tier holds only its own key. Then delete any copies already in + the namespace: + + ```sh + kubectl -n network-adapter delete secret consumer-adapter-keys \ + provider-adapter-keys provider-upstream --ignore-not-found + ``` +4. Point the app at `consumer-adapter.consumer-adapter.svc:9202/` and + the catalogue publisher at `provider-adapter.provider-adapter.svc:9200/publish`. + +To return to single, reverse the steps: put the namespaces back, upgrade +`network-adapter` with `examples/network-adapter.yaml`, uninstall the other two. + ## Values See `values.yaml` — every field is commented with what it does and what breaks -without it. `examples/{provider,network,consumer}.yaml` are working dev deployments; -`ci/` holds the two files lint renders, one minimal and one with every switch -turned on. +without it. `examples/{provider,network,consumer}.yaml` are working dev deployments for the +multi deployment mode, and `examples/network-adapter.yaml` for the single deployment mode (the Network Adapter Layer, default); +`ci/` holds the files lint renders, including `ci/network-adapter-values.yaml`. ## Related diff --git a/charts/adapter-service/ci/lint-values.yaml b/charts/adapter-service/ci/lint-values.yaml index d01cdfad..e4a060ee 100644 --- a/charts/adapter-service/ci/lint-values.yaml +++ b/charts/adapter-service/ci/lint-values.yaml @@ -4,6 +4,7 @@ # role is first among them: it has no default, because a default would hand one # adapter another one's handler role and step list. role: network +networkAdapterDeploymentMode: multi fullnameOverride: network-adapter image: diff --git a/charts/adapter-service/ci/network-adapter-values.yaml b/charts/adapter-service/ci/network-adapter-values.yaml new file mode 100644 index 00000000..4efe2e98 --- /dev/null +++ b/charts/adapter-service/ci/network-adapter-values.yaml @@ -0,0 +1,76 @@ +# charts/adapter-service/ci/network-adapter-values.yaml +# CI only (scripts/lint-charts.sh lints and renders every ci/*-values.yaml). +# Not for installs: use examples/network-adapter.yaml. +# +# Renders the single deployment mode, role network (the Network Adapter +# Layer): config/network-adapter-config.yaml, the generated routing, +# the three tier identities, and the PDB / spread / graceful-shutdown +# settings, so those templates are linted too. Pinned image tag so the render +# is reproducible. +# +# The Ingress is on so the path guard in templates/ingress.yaml runs in CI: the +# allowed path shape (one-segment /, Exact and Prefix) must render. +# ingress.annotations is null on purpose, to cover the `| default dict` on the +# annotations merge. +role: network +networkAdapterDeploymentMode: single +fullnameOverride: network-adapter + +image: + registry: ghcr.io + repository: openagrinet/network-adapter + tag: "v1.9.0" + +# registry.url, otel.*, appName, logLevel, http.timeout.*, routing.* and +# handler.* are not used by this role: the adapter config is mounted from +# config/network-adapter-config.yaml as is (registry URL, OTLP endpoint and +# log level are set there), so they are left out here. Edit that file instead. + +service: + port: 9201 + targetPort: 9201 + +networkAdapterLayer: + keys: + consumer: + existingSecret: consumer-adapter-keys + network: + existingSecret: network-adapter-keys + provider: + existingSecret: provider-adapter-keys + extraSubstitutions: {} + +# Scale defaults for the Network Adapter Layer: roughly the three tiers' +# requests in one pod, at least one replica per node. +resources: + requests: + cpu: 300m + memory: 384Mi + limits: + cpu: 1500m + memory: 1Gi +goMemLimit: 900MiB +replicaCount: 3 +podDisruptionBudget: + enabled: true + minAvailable: 1 +topologySpreadConstraints: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: ScheduleAnyway +terminationGracePeriodSeconds: 40 +preStopSleepSeconds: 10 # needs Kubernetes 1.30+; 0 on older clusters + +ingress: + enabled: true + className: nginx + annotations: null + hosts: + - host: network-adapter.ci.example.com + paths: + - path: /discover + pathType: Exact + - path: /select + pathType: Prefix + - path: /status + pathType: Exact diff --git a/charts/adapter-service/config/network-adapter-config.yaml b/charts/adapter-service/config/network-adapter-config.yaml new file mode 100644 index 00000000..f0817118 --- /dev/null +++ b/charts/adapter-service/config/network-adapter-config.yaml @@ -0,0 +1,381 @@ +# OAN Network Adapter Layer (role network, single deployment mode). The consumer, network +# and provider tiers as four modules of ONE adapter process: +# +# / the consumer (bap); the app posts here, unsigned +# /publish the catalogue system publishes here +# /_internal/network/ verifies and forwards to discovery +# /_internal/provider/ answers from the upstream APIs (bpp) +# +# The paths are flat: /discover, /publish, / and /health. The tiers are +# not part of them. /_internal/ is only ever called by this process's own +# modules (http://localhost:9201/_internal/...); the chart's ingress never +# routes it, and never routes /publish either. +# +# Each module is its tier's module from consumer-config.yaml, network-config.yaml +# and provider-config.yaml. A change to one of those files that should also +# apply here must be made in this file too. +# +# Differences from the three tier files: +# - one listener (9201) and one appName; tier hops call this same listener (see the routing-network-adapter-*.yaml files, which the chart +# generates from routing-consumer.yaml and routing-provider.yaml); +# - an outbound connection pool (httpClientConfig) on every module; +# - log level info. +# +# This file is mounted verbatim. Each module signs as its OWN tier: the +# __CONSUMER_*__, __NETWORK_*__ and __PROVIDER_*__ placeholders are substituted +# by the init container from that tier's Secret +# (networkAdapterLayer.keys..existingSecret) at pod start, so no key is in this +# file, in the ConfigMap, or in git. +appName: "network-adapter" + +log: + level: info + destinations: + - type: stdout + contextKeys: [transaction_id, message_id, subscriber_id, module_id] + +http: + # Also the port the generated routing uses: keep it equal to the + # chart's service.targetPort. + port: 9201 + timeout: + read: 30 + write: 30 + idle: 30 + +pluginManager: + root: ./plugins + +plugins: + # Ships metrics, traces and logs over OTLP/gRPC. On because clickstack is + # deployed alongside. Turn all three off for a cluster without a collector: + # the exporter logs a connection failure every few seconds when nothing + # answers. + otelsetup: + id: otelsetup + config: + serviceName: "oan-network-adapter" + environment: "dev" + otlpEndpoint: "clickstack-otel-collector.observability.svc.cluster.local:4317" + enableMetrics: "true" + enableTracing: "true" + enableLogs: "true" + +modules: + # ------------------------------------------------------------------ consumer + # From consumer-config.yaml. Accepts unsigned requests from the app, signs as + # the consumer, and routes discover to /_internal/network and transactions to + # /_internal/provider. + # + # Mounted at / on purpose: it is the one catch-all mount, and that is + # load-bearing, not accidental. /publish is an exact pattern, so it wins over + # this subtree, and /_internal// are longer subtrees, so they win too. + # Everything else lands here, and the routing config is in effect an action + # allow-list: addRoute runs before sign, so an action with no routing rule is + # an error, never a forwarded request. + - name: consumer + path: / + handler: + type: std + role: bap + subscriberId: "__CONSUMER_SUBSCRIBER_ID__" + # Outbound pool. Go's default keeps 2 idle connections per host, so under + # load most calls would open a new connection. responseHeaderTimeout + # bounds the wait for an answer's headers, which on the consumer -> + # provider hop includes the upstream API call. + httpClientConfig: + maxIdleConns: 512 + maxIdleConnsPerHost: 128 + idleConnTimeout: 90s + responseHeaderTimeout: 30s + plugins: + registry: + id: sunbirdRegistry + config: + url: http://registry.registry.svc.cluster.local:8081/api/v1 + entity: Participant + providerEntity: ProviderSchema + timeout: 2 + retry_max: 1 + retry_wait_min: 100ms + retry_wait_max: 500ms + maxResponseBytes: 1048576 + cacheTTL: 0 + keyManager: + id: simplekeymanager + config: + subscriberId: "__CONSUMER_SUBSCRIBER_ID__" + keyId: "__CONSUMER_KEY_ID__" + signingPrivateKey: "__CONSUMER_SIGNING_PRIVATE__" + signingPublicKey: "__CONSUMER_SIGNING_PUBLIC__" + encrPrivateKey: "__CONSUMER_ENCR_PRIVATE__" + encrPublicKey: "__CONSUMER_ENCR_PUBLIC__" + schemaValidator: + id: schemav2validator + config: + type: url + location: "https://raw.githubusercontent.com/beckn/protocol-specifications-v2/refs/tags/core-v2.0.0-lts/api/v2.0.0/beckn.yaml" + cacheTTL: "3600" + extendedSchema_enabled: "true" + extendedSchema_cacheTTL: "86400" + extendedSchema_maxCacheSize: "100" + extendedSchema_downloadTimeout: "30" + extendedSchema_allowedDomains: "raw.githubusercontent.com,schema.beckn.io,schema.nfh.global,openagrinet.github.io" + signValidator: + id: signvalidator + signer: + id: signer + router: + id: router + config: + routingConfig: /app/config/routing-network-adapter-consumer.yaml + middleware: + - id: reqpreprocessor + config: + contextKeys: transaction_id,message_id + role: bap + steps: + - validateSchema + - addRoute + - sign + + # ------------------------------------------------------------------- network + # From network-config.yaml. Verifies the caller's signature and forwards + # discover and publish to discovery. Internal: never expose it on an Ingress. + - name: network + path: /_internal/network/ + handler: + type: std + role: bpp + subscriberId: "__NETWORK_SUBSCRIBER_ID__" + httpClientConfig: + maxIdleConns: 512 + maxIdleConnsPerHost: 128 + idleConnTimeout: 90s + responseHeaderTimeout: 30s + plugins: + registry: + id: sunbirdRegistry + config: + url: http://registry.registry.svc.cluster.local:8081/api/v1 + entity: Participant + providerEntity: ProviderSchema + timeout: 2 + retry_max: 1 + retry_wait_min: 100ms + retry_wait_max: 500ms + maxResponseBytes: 1048576 + cacheTTL: 0 + keyManager: + id: simplekeymanager + config: + subscriberId: "__NETWORK_SUBSCRIBER_ID__" + keyId: "__NETWORK_KEY_ID__" + signingPrivateKey: "__NETWORK_SIGNING_PRIVATE__" + signingPublicKey: "__NETWORK_SIGNING_PUBLIC__" + encrPrivateKey: "__NETWORK_ENCR_PRIVATE__" + encrPublicKey: "__NETWORK_ENCR_PUBLIC__" + # The extended layer is off: it fetches a resource's own @context per + # payload, and nothing here needs it. The settings below only apply if + # it is turned on. + schemaValidator: + id: schemav2validator + config: + type: url + location: "https://raw.githubusercontent.com/beckn/protocol-specifications-v2/refs/tags/core-v2.0.0-lts/api/v2.0.0/beckn.yaml" + cacheTTL: "3600" + extendedSchema_enabled: "false" + extendedSchema_cacheTTL: "86400" + extendedSchema_maxCacheSize: "100" + extendedSchema_downloadTimeout: "30" + extendedSchema_allowedDomains: "raw.githubusercontent.com,schema.beckn.io,schema.nfh.global,openagrinet.github.io" + signValidator: + id: signvalidator + signer: + id: signer + router: + id: router + config: + # The multi-adapter network routing, unchanged: discover and + # publish to discovery. + routingConfig: /app/config/routing-network.yaml + middleware: + - id: reqpreprocessor + config: + contextKeys: transaction_id,message_id + role: bpp + steps: + - validateSign + - addRoute + + # ------------------------------------------------------------------ provider + # From provider-config.yaml, module 1. Verifies the caller and answers from + # the upstream APIs itself, so it has no router. Upstream credentials come + # from the environment (envFromSecrets: provider-upstream). + - name: provider + path: /_internal/provider/ + handler: + type: std + role: bpp + subscriberId: "__PROVIDER_SUBSCRIBER_ID__" + httpClientConfig: + maxIdleConns: 512 + maxIdleConnsPerHost: 128 + idleConnTimeout: 90s + responseHeaderTimeout: 30s + plugins: + registry: + id: sunbirdRegistry + config: + url: http://registry.registry.svc.cluster.local:8081/api/v1 + entity: Participant + providerEntity: ProviderSchema + timeout: 2 + retry_max: 1 + retry_wait_min: 100ms + retry_wait_max: 500ms + maxResponseBytes: 1048576 + cacheTTL: 0 + keyManager: + id: simplekeymanager + config: + subscriberId: "__PROVIDER_SUBSCRIBER_ID__" + keyId: "__PROVIDER_KEY_ID__" + signingPrivateKey: "__PROVIDER_SIGNING_PRIVATE__" + signingPublicKey: "__PROVIDER_SIGNING_PUBLIC__" + encrPrivateKey: "__PROVIDER_ENCR_PRIVATE__" + encrPublicKey: "__PROVIDER_ENCR_PUBLIC__" + schemaValidator: + id: schemav2validator + config: + type: url + location: "https://raw.githubusercontent.com/beckn/protocol-specifications-v2/refs/tags/core-v2.0.0-lts/api/v2.0.0/beckn.yaml" + cacheTTL: "3600" + extendedSchema_enabled: "true" + extendedSchema_cacheTTL: "86400" + extendedSchema_maxCacheSize: "100" + extendedSchema_downloadTimeout: "30" + extendedSchema_allowedDomains: "raw.githubusercontent.com,schema.beckn.io,schema.nfh.global,openagrinet.github.io" + signValidator: + id: signvalidator + signer: + id: signer + mapper: + id: jsonmapper + config: + fetchTimeout: 10s + maxMappingBytes: 262144 + cacheTTL: 1m + negativeTTL: 1m + maxCacheEntries: 200 + middleware: + - id: reqpreprocessor + config: + contextKeys: transaction_id,message_id + role: bpp + providerSteps: + - id: WeatherObservation + config: + bindingKeys: "mausamgram|openagrinet:WeatherObservation" + mausamgram: + authScheme: header + headerName: Authorization + headerValueEnv: MAUSAMGRAM_AUTH + + - id: MandiPrice + config: + bindingKeys: "agmarknet|openagrinet:MandiPrice" + agmarknet: + authScheme: none + + - id: KnowledgeAdvisory + config: + bindingKeys: "bharat-vistaar|openagrinet:KnowledgeAdvisory" + bharat-vistaar: + authScheme: oauth2 + # Substituted from the provider Secret (vistaarTokenUrl) via + # networkAdapterLayer.extraSubstitutions. + tokenUrl: "__VISTAAR_TOKEN_URL__" + clientIdEnv: VISTAAR_CLIENT_ID + clientSecretEnv: VISTAAR_CLIENT_SECRET + maxResponseBytes: 1048576 + + - id: AgricultureFacility + config: + bindingKeys: "pocra|openagrinet:AgricultureFacility" + pocra: + authScheme: none + searchConcurrency: 1 + steps: + - validateSign + - validateSchema + - WeatherObservation + - MandiPrice + - KnowledgeAdvisory + - AgricultureFacility + + # ---------------------------------------------------------- provider-publish + # From provider-config.yaml, module 2. The catalogue system publishes here; + # signed as the provider and routed to /_internal/network/publish. No + # validateSign: never expose it on an Ingress. At top-level /publish, the + # same path as the multi provider adapter's publish module, so both + # deployment modes present the same URL. + - name: provider-publish + path: /publish + handler: + type: std + role: bap + subscriberId: "__PROVIDER_SUBSCRIBER_ID__" + httpClientConfig: + maxIdleConns: 512 + maxIdleConnsPerHost: 128 + idleConnTimeout: 90s + responseHeaderTimeout: 30s + plugins: + registry: + id: sunbirdRegistry + config: + url: http://registry.registry.svc.cluster.local:8081/api/v1 + entity: Participant + providerEntity: ProviderSchema + timeout: 2 + retry_max: 1 + retry_wait_min: 100ms + retry_wait_max: 500ms + maxResponseBytes: 1048576 + cacheTTL: 0 + keyManager: + id: simplekeymanager + config: + subscriberId: "__PROVIDER_SUBSCRIBER_ID__" + keyId: "__PROVIDER_KEY_ID__" + signingPrivateKey: "__PROVIDER_SIGNING_PRIVATE__" + signingPublicKey: "__PROVIDER_SIGNING_PUBLIC__" + encrPrivateKey: "__PROVIDER_ENCR_PRIVATE__" + encrPublicKey: "__PROVIDER_ENCR_PUBLIC__" + schemaValidator: + id: schemav2validator + config: + type: url + location: "https://raw.githubusercontent.com/beckn/protocol-specifications-v2/refs/tags/core-v2.0.0-lts/api/v2.0.0/beckn.yaml" + cacheTTL: "3600" + extendedSchema_enabled: "true" + extendedSchema_cacheTTL: "86400" + extendedSchema_maxCacheSize: "100" + extendedSchema_downloadTimeout: "30" + extendedSchema_allowedDomains: "raw.githubusercontent.com,schema.beckn.io,schema.nfh.global,openagrinet.github.io" + signer: + id: signer + router: + id: router + config: + routingConfig: /app/config/routing-network-adapter-provider.yaml + middleware: + - id: reqpreprocessor + config: + contextKeys: transaction_id,message_id + role: bap + steps: + - validateSchema + - addRoute + - sign diff --git a/charts/adapter-service/examples/network-adapter.yaml b/charts/adapter-service/examples/network-adapter.yaml new file mode 100644 index 00000000..63d9416b --- /dev/null +++ b/charts/adapter-service/examples/network-adapter.yaml @@ -0,0 +1,111 @@ +# =========================================================================== +# role network, single deployment mode -- the Network Adapter Layer, the DEFAULT +# deployment mode. The optional alternative is multi: examples/{consumer,network,provider}.yaml. +# +# The consumer, network and provider tiers as four modules of ONE adapter +# process, on one Service: +# +# / the consumer (bap); the app posts here, unsigned +# /publish the catalogue system publishes here (internal) +# /_internal/network/ internal: verifies and forwards to discovery +# /_internal/provider/ internal: answers from the upstream APIs (bpp) +# +# The alternative to installing the three multi-adapter releases +# (examples/consumer.yaml, network.yaml, provider.yaml). Run one mode or the +# other in an environment, not both. +# +# helm install network-adapter . -n network-adapter --create-namespace \ +# -f examples/network-adapter.yaml +# +# The config is config/network-adapter-config.yaml; the routing is +# generated from config/routing-*.yaml. Tier hops stay inside the pod, so any +# adapter image works. +# =========================================================================== +role: network +networkAdapterDeploymentMode: single + +# The Service name and DNS name callers use: +# network-adapter.network-adapter.svc.cluster.local:9201 +fullnameOverride: network-adapter + +image: + registry: ghcr.io + repository: openagrinet/network-adapter + tag: "latest" + +# registry.url, otel.*, appName, logLevel, http.timeout.*, routing.* and +# handler.* are not used by this role: the adapter config is mounted from +# config/network-adapter-config.yaml as is (registry URL, OTLP endpoint and +# log level are set there), so they are left out here. Edit that file instead. + +# The same port the quick-start publishes. targetPort is also the port the +# generated routing uses, and must equal http.port in +# config/network-adapter-config.yaml (the render checks). +service: + port: 9201 + targetPort: 9201 + +networkAdapterLayer: + # The three tier Secrets the multi-adapter releases use. openbao-secrets + # reflects them into this namespace. + keys: + consumer: + existingSecret: consumer-adapter-keys + network: + existingSecret: network-adapter-keys + provider: + existingSecret: provider-adapter-keys + # Set per environment: the config's __VISTAAR_TOKEN_URL__ placeholder needs + # an entry here (e.g. __VISTAAR_TOKEN_URL__: provider/vistaarTokenUrl), or + # the init container stops on the leftover placeholder. + extraSubstitutions: {} + +# The provider tier reads its upstream credentials (MAUSAMGRAM_AUTH, +# AGMARKNET_*, VISTAAR_CLIENT_*) from the environment. +envFromSecrets: + - provider-upstream + +resources: + requests: + cpu: 300m + memory: 384Mi + limits: + cpu: 1500m + memory: 1Gi +goMemLimit: 900MiB + +replicaCount: 3 + +podDisruptionBudget: + enabled: true + minAvailable: 1 + +topologySpreadConstraints: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: ScheduleAnyway + +terminationGracePeriodSeconds: 40 +preStopSleepSeconds: 10 # needs Kubernetes 1.30+; 0 on older clusters + +# Off here. When on, only single-segment / paths (the consumer) are +# accepted: /, /publish and /_internal* fail the render. +ingress: + enabled: false + className: "" + hosts: + - host: network-adapter.oan.example.com + paths: + - path: /discover + pathType: Exact + - path: /select + pathType: Exact + - path: /init + pathType: Exact + - path: /confirm + pathType: Exact + - path: /status + pathType: Exact + +waitFor: + enabled: false diff --git a/charts/adapter-service/examples/network.yaml b/charts/adapter-service/examples/network.yaml index 3b7eb061..5d4c15f9 100644 --- a/charts/adapter-service/examples/network.yaml +++ b/charts/adapter-service/examples/network.yaml @@ -1,15 +1,17 @@ # =========================================================================== -# network role -- the network layer of the Beckn network. +# network role, multi deployment mode -- the network tier alone. # # Verifies that the caller's signature checks out against the key the registry # publishes for them, hands discover and publish to the discovery service, and # signs on the way out. It answers neither action itself. # -# helm install network-adapter . -f examples/network.yaml +# helm install network-adapter . -n network-adapter -f examples/network.yaml # # Keep the release name `network-adapter`: consumer routes to this Service by name. # =========================================================================== role: network +# Required for the network tier: the default, single, runs all three tiers. +networkAdapterDeploymentMode: multi # The Service name, and therefore the in-cluster DNS name other adapters use. # Set explicitly because fullname is otherwise "-" -- release diff --git a/charts/adapter-service/templates/NOTES.txt b/charts/adapter-service/templates/NOTES.txt index 9a0a7db3..151c8bbe 100644 --- a/charts/adapter-service/templates/NOTES.txt +++ b/charts/adapter-service/templates/NOTES.txt @@ -1,16 +1,34 @@ +{{- $nal := include "adapter-service.isNetworkAdapterLayer" . -}} {{ .Chart.Name }} {{ .Chart.Version }} — {{ include "adapter-service.fullname" . }} - +{{ if $nal }} + mode single (the default): the Network Adapter Layer + role network, all three tiers in one adapter (/ consumer bap, /publish bap, /_internal/network/ bpp, /_internal/provider/ bpp) + hops between tiers inside the pod (http://localhost:{{ .Values.service.targetPort }}/_internal/) +{{- else }} + mode multi: one release per tier role {{ include "adapter-service.role" . }} ({{ include "adapter-service.handlerRole" . }}) steps {{ include "adapter-service.steps" . | fromYamlArray | join " -> " }} +{{- end }} image {{ include "adapter-service.image" . }} service {{ include "adapter-service.fullname" . }}:{{ .Values.service.port }} +{{- if $nal }} + registry set in config/network-adapter-config.yaml (registry.url is not used by this role) + routes generated from config/routing-{consumer,network,provider}.yaml + telemetry set in config/network-adapter-config.yaml (otel.* is not used by this role) +{{- else }} registry {{ .Values.registry.url }} routes from config/routing-{{ include "adapter-service.role" . }}.yaml telemetry {{ if .Values.otel.enabled }}{{ .Values.otel.endpoint }}{{ else }}off{{ end }} +{{- end }} -This Service is reachable in-cluster as {{ include "adapter-service.fullname" . }}:{{ .Values.service.port }}. +This Service is reachable in-cluster as {{ include "adapter-service.fullname" . }}.{{ include "common.namespace" . }}.svc:{{ .Values.service.port }}. +{{- if $nal }} +The app and the catalogue publisher call it there; its tiers call each other +inside the pod, so no other adapter routes to it. +{{- else }} That name comes from the RELEASE name — whatever routes here must use it, so renaming this release means updating the adapter that points at it. +{{- end }} Check it is serving: @@ -22,6 +40,21 @@ chart's appVersion. TWO THINGS THIS CHART CANNOT CHECK FOR YOU +{{ if $nal -}} +1. The Participant rows. This adapter signs as three identities -- consumer, + network and provider -- from Secrets {{ include "adapter-service.networkAdapterLayer.tierSecret" (dict "ctx" . "tier" "consumer") }}, + {{ include "adapter-service.networkAdapterLayer.tierSecret" (dict "ctx" . "tier" "network") }} and {{ include "adapter-service.networkAdapterLayer.tierSecret" (dict "ctx" . "tier" "provider") }}: the same rows the + multi-adapter releases use. Each must carry its tier's public key. + +2. Key rotation. Changing any of those Secrets restarts nothing: + + kubectl -n {{ include "common.namespace" . }} rollout restart deploy/{{ include "adapter-service.fullname" . }} + +Callers address the tiers by path on this one Service: + app (discover, select, ...) {{ include "adapter-service.fullname" . }}:{{ .Values.service.port }}/ + catalogue publisher {{ include "adapter-service.fullname" . }}:{{ .Values.service.port }}/publish + /_internal/ is the network and provider tiers: never route to it from outside. +{{- else -}} 1. The Participant row. This adapter signs as {{ .Values.keys.existingSecret.name }}'s subscriberId, and every peer verifies that signature against the public key the registry publishes for it. If the row is missing, or carries a different @@ -33,8 +66,9 @@ TWO THINGS THIS CHART CANNOT CHECK FOR YOU checksum. Changing that Secret does not restart anything: kubectl -n {{ include "common.namespace" . }} rollout restart deploy/{{ include "adapter-service.fullname" . }} +{{- end }} -{{- if not .Values.otel.enabled }} +{{- if and (not .Values.otel.enabled) (not $nal) }} Telemetry is off, which is the right setting until a collector exists: with it on and nothing listening the exporter logs a failure every interval. @@ -42,7 +76,14 @@ on and nothing listening the exporter logs a failure every interval. {{- if .Values.ingress.enabled }} Ingress is ON. -{{- if eq (include "adapter-service.role" .) "consumer" }} +{{- if $nal }} +This is the single deployment mode. / (the consumer) accepts UNSIGNED requests: the consumer tier is +inside the trust boundary and has no validateSign step, so an Ingress on it exposes an +unauthenticated entry point to the network. Rate-limit this host. +Rule: only single-segment / paths may be exposed (the render refuses other paths), +never /_internal* or /publish: /_internal/ holds the network and provider tiers, and /publish +has no validateSign and signs as the provider. +{{- else if eq (include "adapter-service.role" .) "consumer" }} This is the consumer role, which accepts UNSIGNED requests — it is inside the trust boundary and has no validateSign step. An Ingress therefore exposes an unauthenticated entry point to the network: rate-limit this host, and do not diff --git a/charts/adapter-service/templates/_helpers.tpl b/charts/adapter-service/templates/_helpers.tpl index ccbf19ad..2f494c0f 100644 --- a/charts/adapter-service/templates/_helpers.tpl +++ b/charts/adapter-service/templates/_helpers.tpl @@ -5,8 +5,10 @@ # Purpose: chart-local helpers delegating to common, plus the role, # identity, upstream and config-rendering wiring an adapter needs. # -# One chart, three roles. provider, network and consumer are the same image and the -# same config format; the role decides the handler role, the step list and +# One chart, three roles: provider, network and consumer. Role network with +# networkAdapterDeploymentMode single (the default) runs all three tiers in one +# adapter; in multi it is the network tier and the three roles run one release +# each. Same image and config format; the role decides the handler role, the step list and # where requests are routed. Install once per role, and keep the RELEASE name # per-role -- the release name is what becomes the Service DNS name that the # other adapters address. @@ -23,7 +25,7 @@ and then mis-handles every request, at the far end, in a peer's logs. {{- define "adapter-service.role" -}} {{- $role := .Values.role | default "" -}} {{- if not $role -}} -{{- fail (printf "%s: role is required -- one of provider, network, consumer. It decides the handler role, the step list and the routing target, so there is no safe default. See examples/ for a values file per role." .Chart.Name) -}} +{{- fail (printf "%s: role is required -- one of provider, network, consumer (role network runs all three tiers when networkAdapterDeploymentMode is single). It decides the handler role, the step list and the routing target, so there is no safe default. See examples/ for a values file per role." .Chart.Name) -}} {{- end -}} {{- if not (has $role (list "provider" "network" "consumer")) -}} {{- fail (printf "%s: role must be one of provider, network, consumer (got %q)." .Chart.Name $role) -}} @@ -31,22 +33,6 @@ and then mis-handles every request, at the far end, in a peer's logs. {{- $role -}} {{- end }} -{{/* -The name the adapter calls itself in its config, its logs and its module. -Defaults to -adapter, matching the compose stack's service names. -*/}} -{{- define "adapter-service.appName" -}} -{{- .Values.appName | default (printf "%s-adapter" (include "adapter-service.role" .)) -}} -{{- end }} - -{{/* -OTEL service name. Defaults to oan--adapter, matching OTEL_SERVICE_NAME -in the compose stack so traces from either deployment line up. -*/}} -{{- define "adapter-service.otelServiceName" -}} -{{- .Values.otel.serviceName | default (printf "oan-%s-adapter" (include "adapter-service.role" .)) -}} -{{- end }} - {{/* bap originates a call, bpp receives one. consumer is the caller; network and provider receive. Overridable, because the role is a shorthand for a default @@ -87,11 +73,6 @@ self-describing when you exec into a pod. routing-{{ include "adapter-service.role" . }}.yaml {{- end }} - -{{- define "adapter-service.name" -}} -{{- include "common.name" . -}} -{{- end }} - {{- define "adapter-service.fullname" -}} {{- include "common.fullname" . -}} {{- end }} @@ -129,34 +110,6 @@ why. {{- $name -}} {{- end }} -{{/* -The registry API base. Required for the same reason as the keys: absent, the -adapter builds a plugin pointed at nothing and every signature verification -fails on a lookup rather than on the signature. -*/}} -{{- define "adapter-service.registryUrl" -}} -{{- $url := .Values.registry.url | default "" -}} -{{- if not $url -}} -{{- fail (printf "%s: registry.url is required -- e.g. http://registry:8081/api/v1. The adapter verifies every caller against the key the registry publishes for them, so with no registry it can verify nobody." .Chart.Name) -}} -{{- end -}} -{{- $url -}} -{{- end }} - -{{/* -OTLP endpoint, required only when telemetry is on. An enabled exporter with -no endpoint dials nothing and logs a failure per export interval, which is -the noise the three enable flags exist to avoid. -*/}} -{{- define "adapter-service.otlpEndpoint" -}} -{{- if .Values.otel.enabled -}} -{{- $ep := .Values.otel.endpoint | default "" -}} -{{- if not $ep -}} -{{- fail (printf "%s: otel.endpoint is required when otel.enabled is true -- e.g. otel-collector.observability.svc.cluster.local:4317. An enabled exporter with nowhere to send logs a failure every export interval." .Chart.Name) -}} -{{- end -}} -{{- $ep -}} -{{- end -}} -{{- end }} - {{/* Where the rendered config lands. An emptyDir, because the init container writes it and readOnlyRootFilesystem means there is nowhere else to write. diff --git a/charts/adapter-service/templates/_network-adapter.tpl b/charts/adapter-service/templates/_network-adapter.tpl new file mode 100644 index 00000000..39b6b358 --- /dev/null +++ b/charts/adapter-service/templates/_network-adapter.tpl @@ -0,0 +1,103 @@ +{{/* +# ============================================================================ +# role network, networkAdapterDeploymentMode single -- the Network Adapter Layer. +# +# The consumer, network and provider tiers as four modules of ONE adapter +# process. The adapter config is config/network-adapter-config.yaml +# (configmap.yaml mounts it like any role's file). The routing is GENERATED at +# render time from config/routing-.yaml, the same files the three +# multi-adapter roles mount, with tier hosts pointed at this pod's own listener. +# ============================================================================ +*/}} + +{{/* +networkAdapterDeploymentMode: single (default) | multi. Only role network +reads it; provider and consumer ignore it. +*/}} +{{- define "adapter-service.deploymentMode" -}} +{{- $mode := .Values.networkAdapterDeploymentMode | default "single" -}} +{{- if not (has $mode (list "single" "multi")) -}} +{{- fail (printf "%s: networkAdapterDeploymentMode must be single or multi (got %q)." .Chart.Name $mode) -}} +{{- end -}} +{{- $mode -}} +{{- end }} + +{{/* +"true" when this release runs the Network Adapter Layer (role network, mode +single), else empty. +*/}} +{{- define "adapter-service.isNetworkAdapterLayer" -}} +{{- if and (eq (include "adapter-service.role" .) "network") (eq (include "adapter-service.deploymentMode" .) "single") -}}true{{- end -}} +{{- end }} + +{{/* +Fails unless every private-key VALUE in a single-mode config is one of the +tier placeholders. Parsed, not grepped: it reads each module's keyManager. +Usage: include "adapter-service.networkAdapterLayer.checkKeys" (dict "ctx" $ "cfg" $parsedConfig) +*/}} +{{- define "adapter-service.networkAdapterLayer.checkKeys" -}} +{{- range $m := .cfg.modules -}} +{{- $km := dig "handler" "plugins" "keyManager" "config" dict $m -}} +{{- range $field := list "signingPrivateKey" "encrPrivateKey" -}} +{{- $v := index $km $field | default "" | toString -}} +{{- if and $v (not (regexMatch "^__(CONSUMER|NETWORK|PROVIDER)_(SIGNING|ENCR)_PRIVATE__$" $v)) -}} +{{- fail (printf "%s: single-mode module %s sets %s to a value that is not a tier placeholder. A private key written into config reaches git, the ConfigMap and `helm get values`; use ___%s__ and the init container fills it from that tier's Secret." $.ctx.Chart.Name $m.name $field (ternary "SIGNING_PRIVATE" "ENCR_PRIVATE" (eq $field "signingPrivateKey"))) -}} +{{- end -}} +{{- end -}} +{{- end -}} +{{- end }} + +{{/* +Routing for the Network Adapter Layer, as YAML: {: }. + +routing-network.yaml is the multi-adapter file, unchanged (discover and publish +to discovery). The other two are generated from routing-consumer.yaml and +routing-provider.yaml by pointing every tier address at THIS adapter's own +listener: http://-adapter[....][:port] becomes +http://localhost:/_internal/, and any path after it is kept +(.../publish becomes /_internal/network/publish). Only the network and provider +tiers are ever targets, so only they are mounted under /_internal/. A target that is not a tier adapter is +kept as it is, and a rule with no target is left alone (the same nil guard as +configmap.yaml). So the two modes are one routing source, and splitting the +modules across pods again changes only the hosts. +*/}} +{{- define "adapter-service.networkAdapterLayer.routing" -}} +{{- $ctx := . -}} +{{- $ownListener := printf "http://localhost:%v/_internal/${1}${4}" .Values.service.targetPort -}} +{{- /* Whole URL, anchored both ends: only a tier host (optionally with a + namespace suffix and port) followed by nothing or a path is rewritten. */ -}} +{{- $tierHost := "^https?://(consumer|network|provider)-adapter(\\.[a-z0-9.-]+)?(:[0-9]+)?(/.*)?$" -}} +{{- $out := dict -}} +{{- range $pair := list (list "routing-consumer.yaml" "routing-network-adapter-consumer.yaml") (list "routing-network.yaml" "routing-network.yaml") (list "routing-provider.yaml" "routing-network-adapter-provider.yaml") -}} +{{- $src := index $pair 0 -}} +{{- $doc := $ctx.Files.Get (printf "config/%s" $src) | fromYaml -}} +{{- if not $doc.routingRules -}} +{{- fail (printf "%s: config/%s parsed to no routingRules; the single-mode routing is generated from it." $ctx.Chart.Name $src) -}} +{{- end -}} +{{- range $i, $rule := $doc.routingRules -}} +{{- if $rule.target -}} +{{- $url := $rule.target.url | default "" -}} +{{- $url = regexReplaceAll $tierHost $url $ownListener -}} +{{- if hasSuffix "/" $url -}} +{{- fail (printf "%s: single-mode routing from config/%s routingRules[%d] ends in a slash (%q). The router appends the action, so that produces //discover." $ctx.Chart.Name $src $i $url) -}} +{{- end -}} +{{- $_ := set $rule.target "url" $url -}} +{{- end -}} +{{- end -}} +{{- $_ := set $out (index $pair 1) $doc -}} +{{- end -}} +{{- toYaml $out -}} +{{- end }} + +{{/* +Secret holding one tier's keypair. Fails naming the tier: a missing identity +would otherwise start, report Ready, and fail every signature that tier makes. +Usage: include "adapter-service.networkAdapterLayer.tierSecret" (dict "ctx" $ "tier" "network") +*/}} +{{- define "adapter-service.networkAdapterLayer.tierSecret" -}} +{{- $name := (index .ctx.Values.networkAdapterLayer.keys .tier).existingSecret | default "" -}} +{{- if not $name -}} +{{- fail (printf "%s: networkAdapterLayer.keys.%s.existingSecret is required -- the %s tier signs as its own identity, from its own Secret (the six keys under keys.existingSecret)." .ctx.Chart.Name .tier .tier) -}} +{{- end -}} +{{- $name -}} +{{- end }} diff --git a/charts/adapter-service/templates/configmap.yaml b/charts/adapter-service/templates/configmap.yaml index f5c47701..aa7fa109 100644 --- a/charts/adapter-service/templates/configmap.yaml +++ b/charts/adapter-service/templates/configmap.yaml @@ -9,6 +9,10 @@ into an emptyDir at start-up. Everything that differs between the provider, network and consumer adapters is a value: they are one image with one config format, and the role decides the identity, the handler role, the step list and where requests are routed. + +role network in single mode reads config/network-adapter-config.yaml; its routing +files are generated from the tier routing files (_network-adapter.tpl), and it +renders more than one routing file. */}} {{/* RAW CONFIG OVERRIDE. @@ -24,6 +28,25 @@ at pod start, which is why no rendered config ever holds a key. A raw config with a literal private key in it would put that key in git, in a ConfigMap, and in `helm get values` -- so the render refuses it. */}} +{{- $networkAdapterLayer := include "adapter-service.isNetworkAdapterLayer" . }} +{{- $nlCfg := "" }} +{{- if $networkAdapterLayer }} +{{- /* single mode: the config is config/network-adapter-config.yaml, + unless `config` replaces it whole. Either way every private key must be a + tier placeholder; checked on the parsed config. */}} +{{- $nlCfg = .Values.config | default (.Files.Get "config/network-adapter-config.yaml") }} +{{- if not $nlCfg }} +{{- fail (printf "%s: role network in single mode reads config/network-adapter-config.yaml, which is missing from the chart." .Chart.Name) }} +{{- end }} +{{- $nlParsed := $nlCfg | fromYaml }} +{{- if or (not $nlParsed) (hasKey $nlParsed "Error") }} +{{- fail (printf "%s: the single-mode adapter config (`config` override or config/network-adapter-config.yaml) does not parse as YAML, so its private keys cannot be checked." .Chart.Name) }} +{{- end }} +{{- include "adapter-service.networkAdapterLayer.checkKeys" (dict "ctx" . "cfg" $nlParsed) }} +{{- if ne (toString (dig "http" "port" "" $nlParsed)) (toString .Values.service.targetPort) }} +{{- fail (printf "%s: the single-mode config listens on http.port %v but service.targetPort is %v. The generated routing sends tier hops to localhost:, so the two must match." .Chart.Name (dig "http" "port" "unset" $nlParsed) .Values.service.targetPort) }} +{{- end }} +{{- else }} {{- $cfgFile := printf "config/%s-config.yaml" (include "adapter-service.role" .) }} {{- $cfg := .Values.config | default (.Files.Get $cfgFile) }} {{- if $cfg }} @@ -33,6 +56,7 @@ in `helm get values` -- so the render refuses it. {{- end }} {{- end }} {{- end }} +{{- end }} apiVersion: v1 kind: ConfigMap metadata: @@ -44,6 +68,18 @@ metadata: {{- toYaml . | nindent 4 }} {{- end }} data: + {{- if $networkAdapterLayer }} + adapter.yaml: | + {{- $nlCfg | nindent 4 }} + {{- if .Values.routing.config }} + {{- fail (printf "%s: routing.config is not used in the single deployment mode. Its routing files are generated from config/routing-{consumer,network,provider}.yaml. To change a route edit those files; to replace the whole adapter config set `config`." .Chart.Name) }} + {{- end }} + {{- range $file, $doc := include "adapter-service.networkAdapterLayer.routing" . | fromYaml }} + {{ $file }}: | + # GENERATED by adapter-service from config/routing-*.yaml. Do not edit; edit those files. + {{- toYaml $doc | nindent 4 }} + {{- end }} + {{- else }} adapter.yaml: | {{- /* The config comes from a FILE, not from this template. @@ -99,3 +135,4 @@ data: {{- end }} {{- end }} {{- $routes | nindent 4 }} + {{- end }} diff --git a/charts/adapter-service/templates/deployment.yaml b/charts/adapter-service/templates/deployment.yaml index e6c623c2..9726b5af 100644 --- a/charts/adapter-service/templates/deployment.yaml +++ b/charts/adapter-service/templates/deployment.yaml @@ -44,6 +44,22 @@ spec: securityContext: {{- . | nindent 8 }} {{- end }} + {{- with .Values.terminationGracePeriodSeconds }} + terminationGracePeriodSeconds: {{ . }} + {{- end }} + {{- with .Values.topologySpreadConstraints }} + topologySpreadConstraints: + {{- range . }} + - {{- toYaml (omit . "labelSelector") | nindent 10 }} + labelSelector: + {{- if .labelSelector }} + {{- toYaml .labelSelector | nindent 12 }} + {{- else }} + matchLabels: + {{- include "adapter-service.selectorLabels" $ | nindent 14 }} + {{- end }} + {{- end }} + {{- end }} initContainers: {{- include "common.waitFor" (dict "ctx" . "tcp" list "http" list) | nindent 8 }} {{/* @@ -70,13 +86,23 @@ spec: - -c - | set -eu + {{- if include "adapter-service.isNetworkAdapterLayer" . }} + # single mode: adapter.yaml plus one routing file per routed module. + cp /config-template/*.yaml /rendered/ + {{- else }} cp /config-template/adapter.yaml /rendered/adapter.yaml cp /config-template/{{ include "adapter-service.routingFile" . }} /rendered/{{ include "adapter-service.routingFile" . }} + {{- end }} subst() { # $1 placeholder, $2 file holding the value if [ ! -s "$2" ]; then + {{- if include "adapter-service.isNetworkAdapterLayer" . }} + # three tier Secrets share key names: name the tier too (e.g. network/keyId) + echo "render-config: ${2#/keys/} is missing or empty in its tier's keys Secret" >&2 + {{- else }} echo "render-config: $(basename "$2") is missing or empty in the keys Secret" >&2 + {{- end }} exit 1 fi # A value the registry has not assigned yet. keyId is the osid it @@ -85,7 +111,11 @@ spec: # -- the sentinel contains a colon -- or an adapter signing with an # identity nothing can verify. if head -c 9 "$2" | grep -q "<&2 + {{- else }} echo "render-config: $(basename "$2") is still unresolved." >&2 + {{- end }} echo " The registry assigns it when registry-seed writes this" >&2 echo " adapter's public key. Run that, then:" >&2 echo " ./scripts/resolve-key-ids.sh -e " >&2 @@ -93,6 +123,24 @@ spec: fi sed -i "s|$1|$(cat "$2")|g" /rendered/adapter.yaml } + {{- if include "adapter-service.isNetworkAdapterLayer" . }} + + {{- /* Three identities: each tier's module signs as that tier, from + that tier's Secret mounted at /keys/. */}} + {{- $k := .Values.keys.existingSecret }} + {{- range $tier := list "consumer" "network" "provider" }} + {{- $T := upper $tier }} + subst __{{ $T }}_SUBSCRIBER_ID__ /keys/{{ $tier }}/{{ $k.subscriberIdKey }} + subst __{{ $T }}_KEY_ID__ /keys/{{ $tier }}/{{ $k.keyIdKey }} + subst __{{ $T }}_SIGNING_PRIVATE__ /keys/{{ $tier }}/{{ $k.signingPrivateKey }} + subst __{{ $T }}_SIGNING_PUBLIC__ /keys/{{ $tier }}/{{ $k.signingPublicKey }} + subst __{{ $T }}_ENCR_PRIVATE__ /keys/{{ $tier }}/{{ $k.encrPrivateKey }} + subst __{{ $T }}_ENCR_PUBLIC__ /keys/{{ $tier }}/{{ $k.encrPublicKey }} + {{- end }} + {{- range $ph, $path := .Values.networkAdapterLayer.extraSubstitutions }} + subst {{ $ph }} /keys/{{ $path }} + {{- end }} + {{- else }} subst __ADAPTER_SUBSCRIBER_ID__ /keys/{{ .Values.keys.existingSecret.subscriberIdKey }} subst __ADAPTER_KEY_ID__ /keys/{{ .Values.keys.existingSecret.keyIdKey }} @@ -107,6 +155,7 @@ spec: {{- range $ph, $key := .Values.keys.existingSecret.extraSubstitutions }} subst {{ $ph }} /keys/{{ $key }} {{- end }} + {{- end }} # A placeholder that survived means a key was substituted with # nothing. Catch it here rather than at the far end, in a peer's @@ -116,14 +165,22 @@ spec: grep -oE '__[A-Z][A-Z0-9_]*__' /rendered/adapter.yaml | sort -u >&2 exit 1 fi - echo "render-config: config rendered for the {{ include "adapter-service.role" . }} role" + echo "render-config: config rendered for the {{ include "adapter-service.role" . }} role{{ if include "adapter-service.isNetworkAdapterLayer" . }} (single mode: all three tiers){{ end }}" volumeMounts: - name: config-template mountPath: /config-template readOnly: true + {{- if include "adapter-service.isNetworkAdapterLayer" . }} + {{- range $tier := list "consumer" "network" "provider" }} + - name: keys-{{ $tier }} + mountPath: /keys/{{ $tier }} + readOnly: true + {{- end }} + {{- else }} - name: keys mountPath: /keys readOnly: true + {{- end }} - name: config mountPath: /rendered resources: @@ -135,6 +192,12 @@ spec: - name: {{ .Chart.Name }} image: {{ include "adapter-service.image" . }} imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if .Values.preStopSleepSeconds }} + lifecycle: + preStop: + sleep: + seconds: {{ .Values.preStopSleepSeconds }} + {{- end }} {{- with (include "common.securityContext" .) }} securityContext: {{- . | nindent 12 }} @@ -150,6 +213,10 @@ spec: */}} - name: CONFIG_FILE value: {{ include "adapter-service.configDir" . }}/adapter.yaml + {{- with .Values.goMemLimit }} + - name: GOMEMLIMIT + value: {{ . | quote }} + {{- end }} {{- with .Values.extraEnv }} {{- toYaml . | nindent 12 }} {{- end }} @@ -176,10 +243,19 @@ spec: - name: config-template configMap: name: {{ include "adapter-service.fullname" . }}-config + {{- if include "adapter-service.isNetworkAdapterLayer" . }} + {{- range $tier := list "consumer" "network" "provider" }} + - name: keys-{{ $tier }} + secret: + secretName: {{ include "adapter-service.networkAdapterLayer.tierSecret" (dict "ctx" $ "tier" $tier) }} + defaultMode: 0400 + {{- end }} + {{- else }} - name: keys secret: secretName: {{ include "adapter-service.keysSecretName" . }} defaultMode: 0400 + {{- end }} {{/* The rendered config. emptyDir rather than anything durable: it is derived from a ConfigMap and a Secret and is rebuilt on every start, diff --git a/charts/adapter-service/templates/ingress.yaml b/charts/adapter-service/templates/ingress.yaml index 529e1f6a..98bacb0d 100644 --- a/charts/adapter-service/templates/ingress.yaml +++ b/charts/adapter-service/templates/ingress.yaml @@ -1,9 +1,42 @@ {{- if .Values.ingress.enabled }} +{{- $ann := merge (dict) (.Values.ingress.annotations | default dict) (.Values.commonAnnotations | default dict) }} +{{- if include "adapter-service.isNetworkAdapterLayer" . }} +{{- /* single mode: the public surface is flat -- /discover, + / and /health, all served by the consumer module at /. Two + things must never be public: /_internal/ (the network and provider + tiers, called only by this process) and /publish (no validateSign, + signs as the provider). So a path is accepted only if it is one literal + segment and is neither of those; "/" is refused because a Prefix "/" + covers both. + Prefix and Exact only: ImplementationSpecific is a plain string prefix + on ingress-nginx, and regex or rewrite annotations turn paths into + patterns, so either could reach /_internal or /publish. */}} +{{- if eq (index $ann "nginx.ingress.kubernetes.io/use-regex" | default "" | toString | trim | lower) "true" }} +{{- fail (printf "%s: ingress annotation nginx.ingress.kubernetes.io/use-regex is not allowed in the single deployment mode. A regex path can match /_internal or /publish." .Chart.Name) }} +{{- end }} +{{- if hasKey $ann "nginx.ingress.kubernetes.io/rewrite-target" }} +{{- fail (printf "%s: ingress annotation nginx.ingress.kubernetes.io/rewrite-target is not allowed in the single deployment mode. It turns paths into regexes and can rewrite a request onto /_internal or /publish." .Chart.Name) }} +{{- end }} +{{- range .Values.ingress.hosts }} +{{- range .paths }} +{{- $p := .path | default "" }} +{{- $pt := .pathType | default "Prefix" }} +{{- if not (has $pt (list "Prefix" "Exact")) }} +{{- fail (printf "%s: ingress path %q has pathType %s, which is not allowed in the single deployment mode. Use Prefix or Exact." $.Chart.Name $p $pt) }} +{{- end }} +{{- if or (not (regexMatch "^/[A-Za-z0-9_.-]+$" $p)) (hasPrefix "/_internal" $p) (eq $p "/publish") }} +{{- fail (printf "%s: ingress path %q is not allowed in the single deployment mode. Allowed: one literal segment, / (e.g. /discover, /select). /, /publish and /_internal* are never public." $.Chart.Name $p) }} +{{- end }} +{{- end }} +{{- end }} +{{- end }} {{/* Off by default, and worth a thought before turning on. -This adapter takes SIGNED requests -- validateSign is its first step -- so -publishing it is not the same risk as publishing an adapter that does not. But +The provider and network roles take SIGNED requests -- validateSign is their +first step -- so publishing them is not the same risk as publishing an adapter +that does not. The consumer role, and the single-mode consumer module +at /, take UNSIGNED requests (NOTES says so when ingress is on). Either way it is still a Beckn peer endpoint, and the thing that makes it safe is that every caller must hold a key the registry publishes. That is a property of the registry's contents, not of this Ingress. @@ -14,7 +47,7 @@ metadata: name: {{ include "adapter-service.fullname" . }} labels: {{- include "adapter-service.labels" . | nindent 4 }} - {{- with (merge (dict) .Values.ingress.annotations (.Values.commonAnnotations | default dict)) }} + {{- with $ann }} annotations: {{- toYaml . | nindent 4 }} {{- end }} diff --git a/charts/adapter-service/values.yaml b/charts/adapter-service/values.yaml index d00b624a..bc5f3521 100644 --- a/charts/adapter-service/values.yaml +++ b/charts/adapter-service/values.yaml @@ -1,19 +1,33 @@ # =========================================================================== # adapter-service # -# One chart for all three Beckn adapters. provider, network and consumer are the -# same image and the same config format; `role` below decides the handler -# role, the step list and where requests are routed. +# One chart for the Beckn adapters, in two deployment modes. Same image and config +# format; `role` below decides the handler role, the step list and where +# requests are routed. # -# INSTALL ONCE PER ROLE, and keep the RELEASE name per-role: +# single (the DEFAULT): ONE release, the Network Adapter Layer, serving all +# three tiers as modules of one process (role network, +# networkAdapterDeploymentMode single): # -# helm install provider-adapter . -f examples/provider.yaml -# helm install network-adapter . -f examples/network.yaml -# helm install consumer-adapter . -f examples/consumer.yaml +# helm install network-adapter . -n network-adapter --create-namespace \ +# -f examples/network-adapter.yaml +# +# multi (OPTIONAL): one release per tier, with networkAdapterDeploymentMode +# multi on the network release. INSTALL ONCE PER ROLE, and keep the RELEASE +# name per-role: +# +# helm install provider-adapter . -f examples/provider.yaml +# helm install network-adapter . -f examples/network.yaml +# helm install consumer-adapter . -f examples/consumer.yaml +# +# Run one deployment mode per environment. Both release `network-adapter` into +# namespace `network-adapter`; switching modes upgrades that release in place. # # The release name is what the Service is called, which is the DNS name the -# other adapters address. Rename a release and you must update whatever routes -# to it -- consumer routes to the network adapter, provider is routed to by network. +# other adapters address. In multi, rename a release and you must update +# whatever routes to it -- consumer routes to the network adapter, provider is +# routed to by network. The single deployment mode has no such addresses: its tiers +# call each other inside the pod. # # Two things every role needs that this chart cannot give it: # @@ -33,6 +47,12 @@ # --------------------------------------------------------------------------- role: "" +# single | multi. Read by role network only: single (the default) runs all +# three tiers in one adapter (networkAdapterLayer below); multi is the network +# tier alone. A multi network release MUST set multi, or an upgrade turns it +# into single. provider and consumer ignore it. +networkAdapterDeploymentMode: single + # Overrides for what the role implies. Leave empty to take the role's default. appName: "" @@ -261,8 +281,8 @@ otel: # OTLP/gRPC, e.g. "otel-collector.observability.svc.cluster.local:4317" endpoint: "" environment: dev - # Left empty, defaults to oan--adapter, matching OTEL_SERVICE_NAME in - # the compose stack so traces from either deployment line up. + # Not read by any template: the role's config file sets it + # (plugins.otelsetup.config.serviceName). serviceName: "" logLevel: info @@ -327,21 +347,37 @@ resources: # Both hit /health, which the adapter serves from its own mux and which does # not touch the registry or discovery -- so a readiness failure here means # this process is unwell, not that something downstream is. -probes: - liveness: - enabled: true +# The startup probe holds liveness off until /health first answers. The +# adapter fetches beckn.yaml from raw.githubusercontent.com at boot, so on +# slow or proxied egress first boot can take well past liveness's ~70s budget +# (10s + 3 x 20s). This bounds it at 180s without loosening steady-state +# liveness. +startupProbe: + enabled: true + httpGet: path: /health - initialDelaySeconds: 10 - periodSeconds: 20 - timeoutSeconds: 3 - failureThreshold: 3 - readiness: - enabled: true + port: http + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 36 +livenessProbe: + enabled: true + httpGet: + path: /health + port: http + initialDelaySeconds: 10 + periodSeconds: 20 + timeoutSeconds: 3 + failureThreshold: 3 +readinessProbe: + enabled: true + httpGet: path: /health - initialDelaySeconds: 5 - periodSeconds: 10 - timeoutSeconds: 3 - failureThreshold: 3 + port: http + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 3 autoscaling: enabled: false @@ -376,6 +412,20 @@ securityContext: drop: - ALL +# Spread replicas so one node loss is not an outage. labelSelector defaults to +# this release's selector labels when omitted. +topologySpreadConstraints: [] + +# Graceful shutdown. preStop sleeps so the endpoint leaves the Service before +# the process stops accepting. preStop.sleep needs Kubernetes 1.30+; keep 0 on +# older clusters. Empty grace period = the Kubernetes default (30s). +terminationGracePeriodSeconds: "" +preStopSleepSeconds: 0 + +# Soft memory limit for the Go runtime (e.g. 900MiB under a 1Gi limit), so GC +# works harder before the container is OOM-killed. Empty = unset. +goMemLimit: "" + nodeSelector: {} tolerations: [] affinity: {} @@ -389,3 +439,41 @@ extraVolumes: [] extraVolumeMounts: [] extraEnv: [] envFromSecrets: [] + +# --------------------------------------------------------------------------- +# role network, networkAdapterDeploymentMode single -- the Network Adapter Layer. +# +# The consumer, network and provider tiers as four modules of ONE process +# (/ for the consumer, /publish, /_internal/network/, /_internal/provider/). Its adapter config is +# config/network-adapter-config.yaml, mounted verbatim like any role's +# file: log level, timeouts, telemetry, extended schema and the outbound +# connection pool are set there. The routing is generated at render time from +# the tier routing files, with tier-to-tier hops pointed at this pod's own +# listener (http://localhost:/_internal/): any adapter image +# works. +# +# Ignored by provider, consumer and a multi network release. +# +# NOT USED in single mode: `routing.config` (setting it fails the render, because +# the routing is generated) and `handler.*` (role, steps and path are in the +# config file). `config` replaces config/network-adapter-config.yaml +# WHOLE: the private-key guard still runs on it, and it must point its routers +# at /app/config/routing-network-adapter-consumer.yaml, routing-network.yaml and +# routing-network-adapter-provider.yaml. +# --------------------------------------------------------------------------- +networkAdapterLayer: + # Three identities, one per tier: the same Secrets (six keys each, named as + # under keys.existingSecret) and the same registry rows the multi-adapter + # releases use, so either mode runs against the same registry. They must + # exist in THIS release's namespace (openbao-secrets reflects them there). + keys: + consumer: + existingSecret: "" + network: + existingSecret: "" + provider: + existingSecret: "" + + # Extra placeholders, as "/". + # __VISTAAR_TOKEN_URL__: provider/vistaarTokenUrl + extraSubstitutions: {} diff --git a/charts/openbao-secrets/CHANGELOG.md b/charts/openbao-secrets/CHANGELOG.md index 9e3e163e..7e2160b0 100644 --- a/charts/openbao-secrets/CHANGELOG.md +++ b/charts/openbao-secrets/CHANGELOG.md @@ -5,6 +5,14 @@ All notable changes to the `openbao-secrets` chart are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.1.1] - 2026-10-09 + +### Added +- `network-adapter` as a target namespace of `consumer-adapter-keys`, + `provider-adapter-keys` and `provider-upstream`: in the single deployment + mode (the default) `adapter-service` role `network` runs all three tiers + there. In multi mode, remove `network-adapter` from those three entries. + ## [0.1.0] - 2026-09-27 ### Added diff --git a/charts/openbao-secrets/Chart.yaml b/charts/openbao-secrets/Chart.yaml index 7a430ef1..b6971ecd 100644 --- a/charts/openbao-secrets/Chart.yaml +++ b/charts/openbao-secrets/Chart.yaml @@ -6,7 +6,7 @@ description: >- This is what turns an OpenBao entry into something a chart can reference by name. type: application -version: 0.1.0 +version: 0.1.1 appVersion: "0.10.7" maintainers: - name: OpenAgriNet Engineering Team diff --git a/charts/openbao-secrets/values.yaml b/charts/openbao-secrets/values.yaml index 75fc2c67..24ee4686 100644 --- a/charts/openbao-secrets/values.yaml +++ b/charts/openbao-secrets/values.yaml @@ -57,14 +57,22 @@ secrets: # -- adapter identities. keyId inside these is filled in by # resolve-key-ids.sh AFTER the registry has been seeded, so these entries # change once per environment after the first bring-up. + # + # network-adapter runs the Network Adapter Layer in the single deployment + # mode (the default), which runs all three tiers, so it reads all three + # identities. In multi mode drop network-adapter from the consumer and + # provider entries, so the network tier never holds their keys + # (charts/adapter-service/README.md, "Multi mode"). - name: consumer-adapter-keys - namespaces: [consumer-adapter] + namespaces: [consumer-adapter, network-adapter] - name: network-adapter-keys namespaces: [network-adapter] - name: provider-adapter-keys - namespaces: [provider-adapter] + namespaces: [provider-adapter, network-adapter] # The provider adapter's upstream credentials, named as environment variables - # by its config rather than substituted into it. + # by its config rather than substituted into it. The Network Adapter Layer + # (namespace network-adapter, single mode) runs the provider tier too, so it reads + # them as well. - name: provider-upstream - namespaces: [provider-adapter] + namespaces: [provider-adapter, network-adapter] diff --git a/docs/adr/0000-template.md b/docs/adr/0000-template.md new file mode 100644 index 00000000..8751ab3f --- /dev/null +++ b/docs/adr/0000-template.md @@ -0,0 +1,24 @@ +# ADR-0000 — Title + +**Status:** Proposed | Accepted | Superseded by ADR-NNNN +**Date:** YYYY-MM-DD +**Issues:** owner/repo#N + +## Context + +What forced a decision. The constraint, not the preamble. + +## Decision + +What was chosen, stated so that a reader can tell whether a given change +complies with it. + +## Alternatives considered + +What was rejected, and the specific property that disqualified it. "It was +worse" is not a reason; "it exposes an unsigned endpoint" is. + +## Consequences + +What this costs, including the cases it makes harder. An ADR that lists only +benefits is advocacy, not a record. diff --git a/docs/adr/0001-single-network-adapter-deployment-mode.md b/docs/adr/0001-single-network-adapter-deployment-mode.md new file mode 100644 index 00000000..02afdca8 --- /dev/null +++ b/docs/adr/0001-single-network-adapter-deployment-mode.md @@ -0,0 +1,79 @@ +# ADR-0001 — One Beckn OAN network adapter process for all three tiers, by default + +**Status:** Proposed +**Date:** 2026-10-15 +**Issues:** OpenAgriNet/helmcharts#55, OpenAgriNet/helmcharts#66, OpenAgriNet/network-adapter#55 (parent OpenAgriNet/engineering-tracker#171) + +## Context + +The Beckn OAN network adapter runs in three tiers: consumer (`bap`, 9202), network (`bpp`, 9201) and provider (`bpp`, 9200). Each was its own container in `quick-start/` and its own Helm release in `charts/adapter-service`, installed once per role. The release name is the Service name the other tiers address. + +So every request crossed two containers or pods, with a network hop and a DNS lookup: +- consumer to network for `discover`; +- consumer to provider for `select`; +- provider to network for `publish`. + +Every environment also ran three of every object: Deployment, Service, ConfigMap, and the PDB or HPA where enabled. + +OAN is single-sided today: our app calls out, and our own discovery and upstream APIs answer. The separate scaling and ownership the split allows is not needed. + +Constraints: +- **The registry cannot update a row.** The identities and participant ids already written must keep working. +- **The published Beckn OAN network adapter image must work unchanged.** + +## Decision + +Two deployment modes from the same chart, compose file, keys and registry rows. **`single` is the default; `multi` (the three adapters, unchanged) is optional.** + +In `single`, one Beckn OAN network adapter process listens on **9201** (the multi network tier's port) and mounts four modules. Each module is its tier's module from the tier configs, with the same steps and its own identity and Secret: + +| Module | Path | Steps | Routes to | +|---|---|---|---| +| consumer | `/` (app calls `/`) | validateSchema, addRoute, sign | `localhost:9201/_internal/network` (discover), `localhost:9201/_internal/provider` (select, init, confirm, status) | +| network | `/_internal/network/` | validateSign, addRoute | discovery | +| provider | `/_internal/provider/` | validateSign, validateSchema, capability steps | answers itself | +| provider-publish | `/publish` | validateSchema, addRoute, sign | `localhost:9201/_internal/network/publish` | + +A change complies with this decision if: +- **Paths:** the paths callers see stay `/discover`, `/publish`, `/` and `/health`, the same as multi. Tier-to-tier paths stay under `/_internal/`, called only by the process itself. +- **Routing:** each routing module keeps its own routing file (`routing-network-adapter-consumer.yaml`, `routing-network-adapter-provider.yaml`; network reuses `routing-network.yaml`). A module's routing file is its list of allowed actions: `addRoute` runs before `sign`, so an action with no rule errors instead of being forwarded. +- **Helm:** no new role. Role `network` with `networkAdapterDeploymentMode: single` (the default) is the single adapter; with `multi` it is the network tier. `provider` and `consumer` ignore the value. Both modes release `network-adapter` into namespace `network-adapter`. Routing is generated at render time from the existing `config/routing-*.yaml`, with hosts rewritten to `localhost`. The render fails on: + - a literal private key; + - `http.port` ≠ `service.targetPort`; + - an unset tier Secret name; + - an Ingress path other than single-segment `/`. +- **Secrets:** `openbao-secrets` reflects the consumer and provider key Secrets and `provider-upstream` into `network-adapter` for single mode. A multi environment removes those targets and deletes the copies, so the network tier holds only its own key. +- **Choosing the mode:** the chart value `networkAdapterDeploymentMode: single | multi` on the `network-adapter` release; multi also installs `consumer-adapter` and `provider-adapter`. In quick-start, `NETWORK_ADAPTER_DEPLOYMENT_MODE` picks `network-adapter`'s config, and profile `multi` adds the other two adapters, via `make up` and `make up-multi`. + +## Alternatives considered + +- **Keep three adapters only.** It keeps three of every object and a cross-pod hop on every request, for a separation a single-sided deployment does not use. +- **In-process transport (`inproc://`) between modules.** It needs an adapter image that includes the transport, so it fails the "image unchanged" constraint. The gain does not pay for that dependency: the `localhost` hop was already sub-millisecond on Docker, the prototype saved about 0.1–0.3 ms per hop, and an end-to-end benchmark showed no measurable gain. +- **Tier prefixes in the public paths** (`/network/`, `/provider/`, `/provider/publish`). This was the first version. It put the tiers back into the URLs callers use, gave single and multi different URLs, and needed a per-path allow-list at the edge instead of one `/_internal` deny. Replaced after review. +- **One Helm role `network` with a mode switch.** With `single` as the default, every existing `role: network` release would become a three-identity adapter on upgrade and fail without the consumer and provider Secrets. It also makes every existing network-tier code path depend on a new setting. +- **One shared routing file for the consumer and publish modules.** The publish module's rule is for the empty action `""`. Shared, it would also apply to the consumer, so `POST /` on the public consumer would publish into the catalogue. +- **Port 9200.** That is the multi provider's port. 9201 is the multi network tier's port and already the chart's default `service.port`. + +## Consequences + +**Gains:** +- One Deployment, Service, ConfigMap and PDB per environment. +- No hop between pods. +- No registry, identity or image change. +- Reversible: install the multi releases next to the single one, repoint the app, remove the single release. + +**Costs:** +- **Scaling is per adapter, not per tier.** An environment that needs a tier scaled on its own uses `multi`. +- **One crash takes all three tiers down.** Replicas (3 in the example), the PDB and the spread constraint limit the blast radius. +- **Tier configs exist twice.** Each module in `network-adapter-config.yaml` mirrors its tier file, so a change meant for both modes is made in both. +- **The catalogue publisher's port differs by mode:** `:9201/publish` in single, `:9200/publish` in multi. + +**Security:** +- `/publish` has no `validateSign`, and `/_internal/` is internal. Their protection is path-level: + - **Helm:** the Ingress guard; + - **quick-start:** the `server_proxy.conf` denies. +- Inside the cluster, anything that reaches port 9201 can call `/publish`. A NetworkPolicy cannot isolate it, because it has no notion of paths. Port-level isolation would need a second listener, which is not built. + +**Outside this repository:** `OpenAgriNet/network-adapter`'s own `install/` and `config/` still describe the per-role layout. Retiring them is the remaining part of network-adapter#55. + +**Verification (2026-10-22, flat paths, port 9201):** quick-start single (11/11 assertions) and multi (10/10), and k3d single (12/12), all passing: publish, discover and select 200, tier hops on `localhost:9201/_internal/`, and on k3d an unsigned call to `/_internal/network/discover` refused with 401. diff --git a/docs/adr/README.md b/docs/adr/README.md new file mode 100644 index 00000000..e4ffadcc --- /dev/null +++ b/docs/adr/README.md @@ -0,0 +1,12 @@ +# Architecture Decision Records + +One file per decision, numbered, never edited after acceptance. A decision +that turns out wrong is **superseded** by a new record, not rewritten. The +point of the log is to answer *why does this look the way it does*, and a +rewritten record cannot answer it. + +Start from [`0000-template.md`](0000-template.md). + +| # | Decision | Issues | +|---|---|---| +| [0001](0001-single-network-adapter-deployment-mode.md) | One Beckn OAN network adapter process for all three tiers, by default (`single`); three adapters stay optional (`multi`) | helmcharts#55, #66 | From 9c538134c19c8eb1dbccce69c25f90cedc7aef3b Mon Sep 17 00:00:00 2001 From: Kelvin Date: Thu, 22 Oct 2026 17:06:12 +0530 Subject: [PATCH 02/10] refactor(adapter-service): move docs to the docs branch, trim comments to two lines (#55, #66) README, CHANGELOGs and docs/adr move to docs/55-network-adapter-deployment-modes. Comments added for the single/multi modes are cut to two lines; the reasoning is in ADR-0001's implementation notes. No rendered output changes. --- charts/adapter-service/CHANGELOG.md | 77 --------- charts/adapter-service/README.md | 149 +++--------------- .../ci/network-adapter-values.yaml | 22 +-- .../config/network-adapter-config.yaml | 79 ++-------- .../examples/network-adapter.yaml | 41 +---- charts/adapter-service/templates/_helpers.tpl | 7 +- .../templates/_network-adapter.tpl | 56 ++----- .../adapter-service/templates/configmap.yaml | 10 +- .../adapter-service/templates/deployment.yaml | 3 +- charts/adapter-service/templates/ingress.yaml | 12 +- charts/adapter-service/values.yaml | 80 +++------- charts/openbao-secrets/CHANGELOG.md | 8 - charts/openbao-secrets/values.yaml | 11 +- docs/adr/0000-template.md | 24 --- ...-single-network-adapter-deployment-mode.md | 79 ---------- docs/adr/README.md | 12 -- 16 files changed, 93 insertions(+), 577 deletions(-) delete mode 100644 docs/adr/0000-template.md delete mode 100644 docs/adr/0001-single-network-adapter-deployment-mode.md delete mode 100644 docs/adr/README.md diff --git a/charts/adapter-service/CHANGELOG.md b/charts/adapter-service/CHANGELOG.md index 5093db13..b8e36b07 100644 --- a/charts/adapter-service/CHANGELOG.md +++ b/charts/adapter-service/CHANGELOG.md @@ -5,83 +5,6 @@ All notable changes to this chart are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this chart adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.6.0] - 2026-10-09 - -### Added -- **Two deployment modes, with single as the default.** - `networkAdapterDeploymentMode: single | multi` (default `single`), read by - role `network` only. In single, role `network` is the Network Adapter Layer: the consumer, network and provider tiers as four - modules of one process (`/` for the consumer, `/publish`, - `/_internal/network/`, `/_internal/provider/`), one release called `network-adapter`. In multi, - `provider`, `network` and `consumer` run one release per tier, unchanged. -- `config/network-adapter-config.yaml`: the layer's adapter config, mounted - like any role's file (an outbound connection pool and log level info on - every module). Each module is its tier's module, so a change to - a tier file that should apply to both deployment modes is made in both. Its routing - is generated at render time from `config/routing-*.yaml` - (`routing-network-adapter-{consumer,provider}.yaml`), with tier hosts rewritten - to `http://localhost:/_internal/`, so tier hops stay inside the pod. The render fails if `http.port` differs from `service.targetPort`. -- Three identities: each module signs as its own tier from - `networkAdapterLayer.keys.{consumer,network,provider}.existingSecret` - (`networkAdapterLayer.extraSubstitutions` for extra placeholders). A literal - private key in the config, including a `config:` override, fails the render. -- Flat paths: `/discover`, `/publish`, `/` and `/health`, the - same URLs as the multi deployment mode. The network and provider tiers are mounted - under `/_internal/`, which only the process itself calls. -- Ingress for the layer may expose only single-segment `/`, as literal - paths with `pathType` Prefix or Exact and no `use-regex` / `rewrite-target` - annotation; `/`, `/publish` and `/_internal*` are refused. `routing.config` fails the render for this - role. -- `examples/network-adapter.yaml` and `ci/network-adapter-values.yaml` - (3 replicas, PDB, spread, graceful shutdown). -- `topologySpreadConstraints`, - `terminationGracePeriodSeconds`, `preStopSleepSeconds`, `goMemLimit`; all off - by default. - -### Changed -- **Breaking:** `role: network` now defaults to the single deployment mode. - An existing network-tier release must set `networkAdapterDeploymentMode: - multi` (as `examples/network.yaml` does), or the upgrade turns it into the - Network Adapter Layer, which needs the three `networkAdapterLayer.keys` - Secrets. -- Docs, `values.yaml` and the example headers say single is the default - deployment mode and multi is optional. -- Naming: in single mode the release, Service, app name and namespace are - `network-adapter`, the same as the multi network tier's (telemetry service - `oan-network-adapter`, set in `config/network-adapter-config.yaml`). - Switching modes upgrades that release in place. - -### Fixed -- Liveness and readiness probes now render. Values used `probes.liveness` / - `probes.readiness`, which `common.probes` never read, so no adapter pod had a - readiness probe. Upgrading rolls every adapter pod once. -- The startup probe bounds first boot at 180s (`periodSeconds: 5` x - `failureThreshold: 36`) and holds off liveness until `/health` answers. - Pods that had no probe before now get a liveness probe, and the adapter - fetches `beckn.yaml` at boot, so without this a slow or proxied egress - would get a cold start killed after about 70s. -- NOTES.txt in single mode no longer prints `registry.url` and - `otel.*`: that role mounts `config/network-adapter-config.yaml` as is, so those - values never reached the pod and NOTES reported the opposite of what it ran. - The example and CI values no longer set them. -- The Ingress annotations merge tolerates `ingress.annotations: null`. - `ci/network-adapter-values.yaml` turns the Ingress on, so CI renders the path guard. - -### Removed -- Unused helpers `adapter-service.name`, `.appName`, `.otelServiceName`, - `.registryUrl` and `.otlpEndpoint` (nothing included them). -- **Breaking:** the `probes.liveness` and `probes.readiness` values keys, renamed to - `livenessProbe` / `readinessProbe`, plus a new `startupProbe`, on by - default. The liveness and readiness timings are unchanged. An environment - that overrides `probes.*` must rename those keys; the old keys never took - effect and are now silently ignored, which is why this is a minor bump - rather than a patch. - -### Unchanged -- Roles `provider`, `network`, `consumer`: same config, routing and manifests - apart from the probes (the chart version label and the config checksum - annotation also change with the version). - ## [0.5.2] - 2026-09-29 ### Changed diff --git a/charts/adapter-service/README.md b/charts/adapter-service/README.md index ac000a2a..7bd5cc81 100644 --- a/charts/adapter-service/README.md +++ b/charts/adapter-service/README.md @@ -1,12 +1,8 @@ # adapter-service -One chart for the Beckn adapters, in two deployment modes. **single (the default):** -the Network Adapter Layer: role `network` runs all three tiers in one adapter. -**multi (optional):** roles `provider`, `network` and `consumer`, one release -per tier. `networkAdapterDeploymentMode: single | multi` (default `single`) -picks the mode; only role `network` reads it. All roles are the same image and the same -config format, so `role` selects the handler role, the step list -and where requests are routed. +One chart for all three Beckn adapters. `provider`, `network` and `consumer` +are the same image and the same config format, so `role` selects the handler +role, the step list and where requests are routed. ``` consumer ──unsigned──▶ network ──▶ discovery @@ -16,33 +12,11 @@ and where requests are routed. └──▶ mandi / agmarknet upstreams ``` -``` - role network, single mode (one adapter, one pod): - / ──lo──▶ /_internal/network/ ──▶ discovery - └──────lo────▶ /_internal/provider/ ──▶ upstream APIs - /publish ──lo──▶ /_internal/network/publish -``` - -The paths are flat and the same in both deployment modes: `/discover`, `/publish`, -`/` and `/health`. The network and provider tiers sit under -`/_internal/`, which only this process calls (over loopback). The ingress may -expose only single-segment `/` paths: `/publish` stays in-cluster for -the catalogue publisher, and `/_internal*` is never routed. - -Why flat: OAN is single-sided today -- our app calls out, and our own -discovery and upstreams answer. If OAN later accepts inbound calls from -external Beckn peers, a peer's `select` and our app's `select` are both -`POST /select`, and path alone cannot tell them apart. The distinguisher exists -(peer calls carry an `Authorization` signature, app calls do not), but ONIX -dispatches on path, so that would mean routing on signature presence at the -edge or giving inbound its own hostname. Neither changes these public paths. - | Role | Handler | Steps | Routes to | |---|---|---|---| | `provider` | `bpp` | `validateSign → addRoute → sign` | upstream APIs (several) | -| `network` (multi) | `bpp` | `validateSign → addRoute → sign` | discovery | +| `network` | `bpp` | `validateSign → addRoute → sign` | discovery | | `consumer` | `bap` | `addRoute → sign` | network | -| `network` (single, default) | `bap` + `bpp` (four modules) | each module runs its own tier's steps | discovery and the upstream APIs; tier hops stay inside the pod | `consumer` is the only one that accepts **unsigned** requests: the consumer app is inside the trust boundary, so there is no network signature to check. @@ -54,46 +28,26 @@ reads from the registry at request time. ## Install -Pick **one** deployment mode per environment (→ [Choosing a deployment mode](#choosing-a-deployment-mode-single-default-or-multi-optional)). -Install the Secrets first: the render fails without them. +Once per role. **Keep the release name per-role** — it is what the Service is +called, which is the DNS name the other adapters address. ```sh helm dependency build charts/adapter-service -``` - -**single (default): one release, the Network Adapter Layer** -```sh -helm upgrade --install network-adapter charts/adapter-service \ - -n network-adapter --create-namespace \ - -f charts/adapter-service/examples/network-adapter.yaml +helm upgrade --install provider-adapter charts/adapter-service -n oan \ + -f charts/adapter-service/examples/provider.yaml +helm upgrade --install network-adapter charts/adapter-service -n oan \ + -f charts/adapter-service/examples/network.yaml +helm upgrade --install consumer-adapter charts/adapter-service -n oan \ + -f charts/adapter-service/examples/consumer.yaml ``` -It needs `consumer-adapter-keys`, `network-adapter-keys`, -`provider-adapter-keys` and `provider-upstream` in `network-adapter` -(openbao-secrets mirrors them there). - -**multi (optional): one release per tier, each in its own namespace** (see -[Multi mode](#multi-mode)). **Keep -the release and namespace names**: they make the Service DNS name the other -adapters address (the consumer routes to -`network-adapter.network-adapter.svc` and `provider-adapter.provider-adapter.svc`). - -```sh -helm upgrade --install provider-adapter charts/adapter-service \ - -n provider-adapter --create-namespace -f charts/adapter-service/examples/provider.yaml -helm upgrade --install network-adapter charts/adapter-service \ - -n network-adapter --create-namespace -f charts/adapter-service/examples/network.yaml -helm upgrade --install consumer-adapter charts/adapter-service \ - -n consumer-adapter --create-namespace -f charts/adapter-service/examples/consumer.yaml -``` - -Each example sets `fullnameOverride` to `-adapter` (`network-adapter` for the layer). Without it the Service +Each example sets `fullnameOverride` to `-adapter`. Without it the Service would be named `-adapter-service`, and nothing routing by name would -resolve. In multi, rename a release and you must update whatever routes to it — -`consumer` points at the network adapter, `network` points at discovery. In -single nothing routes to another release: the tiers call each other inside the -pod. +resolve. Rename a release and you must update whatever routes to it — `consumer` +points at the network adapter, `network` points at discovery. + +The Secret comes first — the render fails without it. ## What you must decide @@ -104,7 +58,7 @@ render error rather than a default: | Value | Why there is no default | |---|---| | `image.repository` | An empty one renders `ghcr.io/:tag`, which Helm and the API server both accept and which surfaces later as `ImagePullBackOff` | -| `keys.existingSecret.name` | Without an identity the pod starts, serves `/health`, reports Ready, and fails every signature it makes — in a peer's logs. Role `network` in single mode uses `networkAdapterLayer.keys.{consumer,network,provider}.existingSecret` instead (three identities) | +| `keys.existingSecret.name` | Without an identity the pod starts, serves `/health`, reports Ready, and fails every signature it makes — in a peer's logs | | `registry.url` | The adapter verifies every caller against the registry, so with none it can verify nobody | | `role` | It decides the handler role, the step list and the routing target. A default would give one adapter another one's behaviour | | `routing.rules` | An adapter with no route accepts requests and has nowhere to send them | @@ -167,73 +121,12 @@ the **public** half of the keypair. Missing, or carrying a different key, and every peer rejects what this adapter signs while the pod stays perfectly healthy. -## Choosing a deployment mode: single (default) or multi (optional) - -| Deployment mode | Releases → namespaces | Values | App calls | Catalogue publishes to | -|---|---|---|---|---| -| **single (default)**: Network Adapter Layer | `network-adapter` → `network-adapter` | `examples/network-adapter.yaml` | `network-adapter.network-adapter.svc:9201/` | `network-adapter.network-adapter.svc:9201/publish` | -| multi (optional) | `consumer-adapter`, `network-adapter`, `provider-adapter` → one namespace each, same name | `examples/{consumer,network,provider}.yaml` | `consumer-adapter.consumer-adapter.svc:9202/` | `provider-adapter.provider-adapter.svc:9200/publish` | - -**When to use which** - -| Use **single** (default) when | Use **multi** when | -|---|---| -| One team runs all three tiers in one cluster | The tiers are run by different parties or in different trust zones | -| You want fewer hops and fewer objects (one Deployment, Service, PDB, and an opt-in HPA) | A tier needs its own scaling or resource profile (e.g. a provider with heavy upstream calls) | -| | You need to roll out or roll back one tier without the others | - -Both sign as the same three identities from the same registry rows, so the -choice changes nothing in the registry. - -Role `network` in single mode runs all three tiers in one adapter, as four -modules of one process. Its config is `config/network-adapter-config.yaml` -(each module is its tier's module from `config/-config.yaml`, so a change -to a tier file that should apply to both deployment modes goes in both files). Its routing -is **generated** at render time from `config/routing-*.yaml`, so routes stay in -one place. Tier hops go to the pod's own listener, `localhost:` (any image). It signs as -the same three identities, from the same three Secrets, so the registry is the -same in both deployment modes. An environment's `config:` override for a multi role is NOT -seen by the Network Adapter Layer; give its release its own `config:` if -needed (it replaces `network-adapter-config.yaml` whole). - -Pick the deployment mode with ONE value, `networkAdapterDeploymentMode: single | -multi` (**default `single`**), on the `network-adapter` release. `single`: that -release is the Network Adapter Layer and nothing else is installed. `multi`: it -is the network tier, and `consumer-adapter` and `provider-adapter` are -installed beside it. `provider` and `consumer` ignore the value. Any other -value fails the render. - -Both modes release `network-adapter` into namespace `network-adapter`, so -switching modes is `helm upgrade` of that release with the other example, -plus installing or removing the other two releases. - -### Multi mode - -1. Set `networkAdapterDeploymentMode: multi` on the network release - (`examples/network.yaml` does). **An existing `role: network` release - without it becomes single on upgrade.** -2. Install the three releases as above. -3. In openbao-secrets, remove `network-adapter` from the namespaces of - `consumer-adapter-keys`, `provider-adapter-keys` and `provider-upstream`, so - the network tier holds only its own key. Then delete any copies already in - the namespace: - - ```sh - kubectl -n network-adapter delete secret consumer-adapter-keys \ - provider-adapter-keys provider-upstream --ignore-not-found - ``` -4. Point the app at `consumer-adapter.consumer-adapter.svc:9202/` and - the catalogue publisher at `provider-adapter.provider-adapter.svc:9200/publish`. - -To return to single, reverse the steps: put the namespaces back, upgrade -`network-adapter` with `examples/network-adapter.yaml`, uninstall the other two. - ## Values See `values.yaml` — every field is commented with what it does and what breaks -without it. `examples/{provider,network,consumer}.yaml` are working dev deployments for the -multi deployment mode, and `examples/network-adapter.yaml` for the single deployment mode (the Network Adapter Layer, default); -`ci/` holds the files lint renders, including `ci/network-adapter-values.yaml`. +without it. `examples/{provider,network,consumer}.yaml` are working dev deployments; +`ci/` holds the two files lint renders, one minimal and one with every switch +turned on. ## Related diff --git a/charts/adapter-service/ci/network-adapter-values.yaml b/charts/adapter-service/ci/network-adapter-values.yaml index 4efe2e98..78519a25 100644 --- a/charts/adapter-service/ci/network-adapter-values.yaml +++ b/charts/adapter-service/ci/network-adapter-values.yaml @@ -1,17 +1,5 @@ -# charts/adapter-service/ci/network-adapter-values.yaml -# CI only (scripts/lint-charts.sh lints and renders every ci/*-values.yaml). -# Not for installs: use examples/network-adapter.yaml. -# -# Renders the single deployment mode, role network (the Network Adapter -# Layer): config/network-adapter-config.yaml, the generated routing, -# the three tier identities, and the PDB / spread / graceful-shutdown -# settings, so those templates are linted too. Pinned image tag so the render -# is reproducible. -# -# The Ingress is on so the path guard in templates/ingress.yaml runs in CI: the -# allowed path shape (one-segment /, Exact and Prefix) must render. -# ingress.annotations is null on purpose, to cover the `| default dict` on the -# annotations merge. +# CI only: renders single mode (generated routing, tier identities, PDB, spread, +# shutdown) with ingress on and annotations null to exercise the path guard. role: network networkAdapterDeploymentMode: single fullnameOverride: network-adapter @@ -21,10 +9,8 @@ image: repository: openagrinet/network-adapter tag: "v1.9.0" -# registry.url, otel.*, appName, logLevel, http.timeout.*, routing.* and -# handler.* are not used by this role: the adapter config is mounted from -# config/network-adapter-config.yaml as is (registry URL, OTLP endpoint and -# log level are set there), so they are left out here. Edit that file instead. +# registry.url, otel.*, appName, logLevel, http.timeout.*, routing.* and handler.* +# are unused in single mode: set them in config/network-adapter-config.yaml. service: port: 9201 diff --git a/charts/adapter-service/config/network-adapter-config.yaml b/charts/adapter-service/config/network-adapter-config.yaml index f0817118..8eab2778 100644 --- a/charts/adapter-service/config/network-adapter-config.yaml +++ b/charts/adapter-service/config/network-adapter-config.yaml @@ -1,31 +1,5 @@ -# OAN Network Adapter Layer (role network, single deployment mode). The consumer, network -# and provider tiers as four modules of ONE adapter process: -# -# / the consumer (bap); the app posts here, unsigned -# /publish the catalogue system publishes here -# /_internal/network/ verifies and forwards to discovery -# /_internal/provider/ answers from the upstream APIs (bpp) -# -# The paths are flat: /discover, /publish, / and /health. The tiers are -# not part of them. /_internal/ is only ever called by this process's own -# modules (http://localhost:9201/_internal/...); the chart's ingress never -# routes it, and never routes /publish either. -# -# Each module is its tier's module from consumer-config.yaml, network-config.yaml -# and provider-config.yaml. A change to one of those files that should also -# apply here must be made in this file too. -# -# Differences from the three tier files: -# - one listener (9201) and one appName; tier hops call this same listener (see the routing-network-adapter-*.yaml files, which the chart -# generates from routing-consumer.yaml and routing-provider.yaml); -# - an outbound connection pool (httpClientConfig) on every module; -# - log level info. -# -# This file is mounted verbatim. Each module signs as its OWN tier: the -# __CONSUMER_*__, __NETWORK_*__ and __PROVIDER_*__ placeholders are substituted -# by the init container from that tier's Secret -# (networkAdapterLayer.keys..existingSecret) at pod start, so no key is in this -# file, in the ConfigMap, or in git. +# Single mode: consumer at /, /publish, /_internal/network/, /_internal/provider/ in one +# process. Each module mirrors its tier file; keys are filled in at pod start (ADR-0001). appName: "network-adapter" log: @@ -47,10 +21,8 @@ pluginManager: root: ./plugins plugins: - # Ships metrics, traces and logs over OTLP/gRPC. On because clickstack is - # deployed alongside. Turn all three off for a cluster without a collector: - # the exporter logs a connection failure every few seconds when nothing - # answers. + # OTLP/gRPC metrics, traces and logs. Turn all three off where no collector + # runs, or the exporter logs a failure every few seconds. otelsetup: id: otelsetup config: @@ -62,27 +34,16 @@ plugins: enableLogs: "true" modules: - # ------------------------------------------------------------------ consumer - # From consumer-config.yaml. Accepts unsigned requests from the app, signs as - # the consumer, and routes discover to /_internal/network and transactions to - # /_internal/provider. - # - # Mounted at / on purpose: it is the one catch-all mount, and that is - # load-bearing, not accidental. /publish is an exact pattern, so it wins over - # this subtree, and /_internal// are longer subtrees, so they win too. - # Everything else lands here, and the routing config is in effect an action - # allow-list: addRoute runs before sign, so an action with no routing rule is - # an error, never a forwarded request. + # consumer: unsigned app calls at / (the catch-all mount, on purpose: see ADR-0001); + # discover to /_internal/network, transactions to /_internal/provider. - name: consumer path: / handler: type: std role: bap subscriberId: "__CONSUMER_SUBSCRIBER_ID__" - # Outbound pool. Go's default keeps 2 idle connections per host, so under - # load most calls would open a new connection. responseHeaderTimeout - # bounds the wait for an answer's headers, which on the consumer -> - # provider hop includes the upstream API call. + # Outbound connection pool; responseHeaderTimeout includes the upstream + # API call on the consumer -> provider hop. httpClientConfig: maxIdleConns: 512 maxIdleConnsPerHost: 128 @@ -139,9 +100,8 @@ modules: - addRoute - sign - # ------------------------------------------------------------------- network - # From network-config.yaml. Verifies the caller's signature and forwards - # discover and publish to discovery. Internal: never expose it on an Ingress. + # network: verifies the caller, forwards discover and publish to discovery. + # Internal: never expose it on an Ingress. - name: network path: /_internal/network/ handler: @@ -175,9 +135,8 @@ modules: signingPublicKey: "__NETWORK_SIGNING_PUBLIC__" encrPrivateKey: "__NETWORK_ENCR_PRIVATE__" encrPublicKey: "__NETWORK_ENCR_PUBLIC__" - # The extended layer is off: it fetches a resource's own @context per - # payload, and nothing here needs it. The settings below only apply if - # it is turned on. + # Extended layer off: it fetches each resource's @context per payload. + # The extendedSchema_* settings apply only when it is on. schemaValidator: id: schemav2validator config: @@ -208,10 +167,8 @@ modules: - validateSign - addRoute - # ------------------------------------------------------------------ provider - # From provider-config.yaml, module 1. Verifies the caller and answers from - # the upstream APIs itself, so it has no router. Upstream credentials come - # from the environment (envFromSecrets: provider-upstream). + # provider: verifies the caller and answers from the upstream APIs (no router); + # upstream credentials come from envFromSecrets: provider-upstream. - name: provider path: /_internal/provider/ handler: @@ -314,12 +271,8 @@ modules: - KnowledgeAdvisory - AgricultureFacility - # ---------------------------------------------------------- provider-publish - # From provider-config.yaml, module 2. The catalogue system publishes here; - # signed as the provider and routed to /_internal/network/publish. No - # validateSign: never expose it on an Ingress. At top-level /publish, the - # same path as the multi provider adapter's publish module, so both - # deployment modes present the same URL. + # provider-publish: the catalogue system publishes at /publish, signed as the + # provider. No validateSign: never expose it on an Ingress. - name: provider-publish path: /publish handler: diff --git a/charts/adapter-service/examples/network-adapter.yaml b/charts/adapter-service/examples/network-adapter.yaml index 63d9416b..7fef92da 100644 --- a/charts/adapter-service/examples/network-adapter.yaml +++ b/charts/adapter-service/examples/network-adapter.yaml @@ -1,26 +1,5 @@ -# =========================================================================== -# role network, single deployment mode -- the Network Adapter Layer, the DEFAULT -# deployment mode. The optional alternative is multi: examples/{consumer,network,provider}.yaml. -# -# The consumer, network and provider tiers as four modules of ONE adapter -# process, on one Service: -# -# / the consumer (bap); the app posts here, unsigned -# /publish the catalogue system publishes here (internal) -# /_internal/network/ internal: verifies and forwards to discovery -# /_internal/provider/ internal: answers from the upstream APIs (bpp) -# -# The alternative to installing the three multi-adapter releases -# (examples/consumer.yaml, network.yaml, provider.yaml). Run one mode or the -# other in an environment, not both. -# -# helm install network-adapter . -n network-adapter --create-namespace \ -# -f examples/network-adapter.yaml -# -# The config is config/network-adapter-config.yaml; the routing is -# generated from config/routing-*.yaml. Tier hops stay inside the pod, so any -# adapter image works. -# =========================================================================== +# Single deployment mode (default): the consumer, network and provider tiers in +# one release. Install: helm install network-adapter . -n network-adapter -f examples/network-adapter.yaml role: network networkAdapterDeploymentMode: single @@ -33,14 +12,11 @@ image: repository: openagrinet/network-adapter tag: "latest" -# registry.url, otel.*, appName, logLevel, http.timeout.*, routing.* and -# handler.* are not used by this role: the adapter config is mounted from -# config/network-adapter-config.yaml as is (registry URL, OTLP endpoint and -# log level are set there), so they are left out here. Edit that file instead. +# registry.url, otel.*, appName, logLevel, http.timeout.*, routing.* and handler.* +# are unused in single mode: set them in config/network-adapter-config.yaml. -# The same port the quick-start publishes. targetPort is also the port the -# generated routing uses, and must equal http.port in -# config/network-adapter-config.yaml (the render checks). +# targetPort must equal http.port in config/network-adapter-config.yaml (the +# render checks); the generated routing uses it. service: port: 9201 targetPort: 9201 @@ -55,9 +31,8 @@ networkAdapterLayer: existingSecret: network-adapter-keys provider: existingSecret: provider-adapter-keys - # Set per environment: the config's __VISTAAR_TOKEN_URL__ placeholder needs - # an entry here (e.g. __VISTAAR_TOKEN_URL__: provider/vistaarTokenUrl), or - # the init container stops on the leftover placeholder. + # Set per environment, e.g. __VISTAAR_TOKEN_URL__: provider/vistaarTokenUrl, + # or the init container stops on the leftover placeholder. extraSubstitutions: {} # The provider tier reads its upstream credentials (MAUSAMGRAM_AUTH, diff --git a/charts/adapter-service/templates/_helpers.tpl b/charts/adapter-service/templates/_helpers.tpl index 2f494c0f..377530b7 100644 --- a/charts/adapter-service/templates/_helpers.tpl +++ b/charts/adapter-service/templates/_helpers.tpl @@ -5,11 +5,8 @@ # Purpose: chart-local helpers delegating to common, plus the role, # identity, upstream and config-rendering wiring an adapter needs. # -# One chart, three roles: provider, network and consumer. Role network with -# networkAdapterDeploymentMode single (the default) runs all three tiers in one -# adapter; in multi it is the network tier and the three roles run one release -# each. Same image and config format; the role decides the handler role, the step list and -# where requests are routed. Install once per role, and keep the RELEASE name +# One chart, three roles: provider, network and consumer (role network runs all +# three tiers in single mode). The role decides handler role, steps and routing. Install once per role, and keep the RELEASE name # per-role -- the release name is what becomes the Service DNS name that the # other adapters address. # ============================================================================ diff --git a/charts/adapter-service/templates/_network-adapter.tpl b/charts/adapter-service/templates/_network-adapter.tpl index 39b6b358..9ea8da92 100644 --- a/charts/adapter-service/templates/_network-adapter.tpl +++ b/charts/adapter-service/templates/_network-adapter.tpl @@ -1,19 +1,7 @@ -{{/* -# ============================================================================ -# role network, networkAdapterDeploymentMode single -- the Network Adapter Layer. -# -# The consumer, network and provider tiers as four modules of ONE adapter -# process. The adapter config is config/network-adapter-config.yaml -# (configmap.yaml mounts it like any role's file). The routing is GENERATED at -# render time from config/routing-.yaml, the same files the three -# multi-adapter roles mount, with tier hosts pointed at this pod's own listener. -# ============================================================================ -*/}} +{{/* Single mode (role network): three tiers in one adapter; config from +config/network-adapter-config.yaml, routing generated below (ADR-0001). */}} -{{/* -networkAdapterDeploymentMode: single (default) | multi. Only role network -reads it; provider and consumer ignore it. -*/}} +{{/* networkAdapterDeploymentMode: single (default) | multi; only role network reads it. */}} {{- define "adapter-service.deploymentMode" -}} {{- $mode := .Values.networkAdapterDeploymentMode | default "single" -}} {{- if not (has $mode (list "single" "multi")) -}} @@ -22,19 +10,13 @@ reads it; provider and consumer ignore it. {{- $mode -}} {{- end }} -{{/* -"true" when this release runs the Network Adapter Layer (role network, mode -single), else empty. -*/}} +{{/* "true" for role network in single mode, else empty. */}} {{- define "adapter-service.isNetworkAdapterLayer" -}} {{- if and (eq (include "adapter-service.role" .) "network") (eq (include "adapter-service.deploymentMode" .) "single") -}}true{{- end -}} {{- end }} -{{/* -Fails unless every private-key VALUE in a single-mode config is one of the -tier placeholders. Parsed, not grepped: it reads each module's keyManager. -Usage: include "adapter-service.networkAdapterLayer.checkKeys" (dict "ctx" $ "cfg" $parsedConfig) -*/}} +{{/* Fails unless every module's private keys are tier placeholders. +Usage: include "adapter-service.networkAdapterLayer.checkKeys" (dict "ctx" $ "cfg" $parsedConfig) */}} {{- define "adapter-service.networkAdapterLayer.checkKeys" -}} {{- range $m := .cfg.modules -}} {{- $km := dig "handler" "plugins" "keyManager" "config" dict $m -}} @@ -47,25 +29,12 @@ Usage: include "adapter-service.networkAdapterLayer.checkKeys" (dict "ctx" $ "cf {{- end -}} {{- end }} -{{/* -Routing for the Network Adapter Layer, as YAML: {: }. - -routing-network.yaml is the multi-adapter file, unchanged (discover and publish -to discovery). The other two are generated from routing-consumer.yaml and -routing-provider.yaml by pointing every tier address at THIS adapter's own -listener: http://-adapter[....][:port] becomes -http://localhost:/_internal/, and any path after it is kept -(.../publish becomes /_internal/network/publish). Only the network and provider -tiers are ever targets, so only they are mounted under /_internal/. A target that is not a tier adapter is -kept as it is, and a rule with no target is left alone (the same nil guard as -configmap.yaml). So the two modes are one routing source, and splitting the -modules across pods again changes only the hosts. -*/}} +{{/* Single-mode routing as {: }: tier hosts in routing-{consumer,provider}.yaml +become http://localhost:/_internal/ (ADR-0001); routing-network.yaml as is. */}} {{- define "adapter-service.networkAdapterLayer.routing" -}} {{- $ctx := . -}} {{- $ownListener := printf "http://localhost:%v/_internal/${1}${4}" .Values.service.targetPort -}} -{{- /* Whole URL, anchored both ends: only a tier host (optionally with a - namespace suffix and port) followed by nothing or a path is rewritten. */ -}} +{{- /* Anchored: only a tier host (optional namespace and port) plus optional path. */ -}} {{- $tierHost := "^https?://(consumer|network|provider)-adapter(\\.[a-z0-9.-]+)?(:[0-9]+)?(/.*)?$" -}} {{- $out := dict -}} {{- range $pair := list (list "routing-consumer.yaml" "routing-network-adapter-consumer.yaml") (list "routing-network.yaml" "routing-network.yaml") (list "routing-provider.yaml" "routing-network-adapter-provider.yaml") -}} @@ -89,11 +58,8 @@ modules across pods again changes only the hosts. {{- toYaml $out -}} {{- end }} -{{/* -Secret holding one tier's keypair. Fails naming the tier: a missing identity -would otherwise start, report Ready, and fail every signature that tier makes. -Usage: include "adapter-service.networkAdapterLayer.tierSecret" (dict "ctx" $ "tier" "network") -*/}} +{{/* One tier's key Secret; fails naming the tier when unset. +Usage: include "adapter-service.networkAdapterLayer.tierSecret" (dict "ctx" $ "tier" "network") */}} {{- define "adapter-service.networkAdapterLayer.tierSecret" -}} {{- $name := (index .ctx.Values.networkAdapterLayer.keys .tier).existingSecret | default "" -}} {{- if not $name -}} diff --git a/charts/adapter-service/templates/configmap.yaml b/charts/adapter-service/templates/configmap.yaml index aa7fa109..df057297 100644 --- a/charts/adapter-service/templates/configmap.yaml +++ b/charts/adapter-service/templates/configmap.yaml @@ -10,9 +10,8 @@ Everything that differs between the provider, network and consumer adapters is a value: they are one image with one config format, and the role decides the identity, the handler role, the step list and where requests are routed. -role network in single mode reads config/network-adapter-config.yaml; its routing -files are generated from the tier routing files (_network-adapter.tpl), and it -renders more than one routing file. +Single mode reads config/network-adapter-config.yaml and renders the generated +routing files (_network-adapter.tpl). */}} {{/* RAW CONFIG OVERRIDE. @@ -31,9 +30,8 @@ in `helm get values` -- so the render refuses it. {{- $networkAdapterLayer := include "adapter-service.isNetworkAdapterLayer" . }} {{- $nlCfg := "" }} {{- if $networkAdapterLayer }} -{{- /* single mode: the config is config/network-adapter-config.yaml, - unless `config` replaces it whole. Either way every private key must be a - tier placeholder; checked on the parsed config. */}} +{{- /* Single mode: config/network-adapter-config.yaml unless `config` replaces it; + private keys must be tier placeholders either way. */}} {{- $nlCfg = .Values.config | default (.Files.Get "config/network-adapter-config.yaml") }} {{- if not $nlCfg }} {{- fail (printf "%s: role network in single mode reads config/network-adapter-config.yaml, which is missing from the chart." .Chart.Name) }} diff --git a/charts/adapter-service/templates/deployment.yaml b/charts/adapter-service/templates/deployment.yaml index 9726b5af..6392ee3a 100644 --- a/charts/adapter-service/templates/deployment.yaml +++ b/charts/adapter-service/templates/deployment.yaml @@ -125,8 +125,7 @@ spec: } {{- if include "adapter-service.isNetworkAdapterLayer" . }} - {{- /* Three identities: each tier's module signs as that tier, from - that tier's Secret mounted at /keys/. */}} + {{- /* Each tier's module signs from its own Secret at /keys/. */}} {{- $k := .Values.keys.existingSecret }} {{- range $tier := list "consumer" "network" "provider" }} {{- $T := upper $tier }} diff --git a/charts/adapter-service/templates/ingress.yaml b/charts/adapter-service/templates/ingress.yaml index 98bacb0d..0967750f 100644 --- a/charts/adapter-service/templates/ingress.yaml +++ b/charts/adapter-service/templates/ingress.yaml @@ -1,16 +1,8 @@ {{- if .Values.ingress.enabled }} {{- $ann := merge (dict) (.Values.ingress.annotations | default dict) (.Values.commonAnnotations | default dict) }} {{- if include "adapter-service.isNetworkAdapterLayer" . }} -{{- /* single mode: the public surface is flat -- /discover, - / and /health, all served by the consumer module at /. Two - things must never be public: /_internal/ (the network and provider - tiers, called only by this process) and /publish (no validateSign, - signs as the provider). So a path is accepted only if it is one literal - segment and is neither of those; "/" is refused because a Prefix "/" - covers both. - Prefix and Exact only: ImplementationSpecific is a plain string prefix - on ingress-nginx, and regex or rewrite annotations turn paths into - patterns, so either could reach /_internal or /publish. */}} +{{- /* Single mode: only literal one-segment / paths (Prefix/Exact, no regex or + rewrite); never /, /publish or /_internal* (ADR-0001). */}} {{- if eq (index $ann "nginx.ingress.kubernetes.io/use-regex" | default "" | toString | trim | lower) "true" }} {{- fail (printf "%s: ingress annotation nginx.ingress.kubernetes.io/use-regex is not allowed in the single deployment mode. A regex path can match /_internal or /publish." .Chart.Name) }} {{- end }} diff --git a/charts/adapter-service/values.yaml b/charts/adapter-service/values.yaml index bc5f3521..9cd47ad9 100644 --- a/charts/adapter-service/values.yaml +++ b/charts/adapter-service/values.yaml @@ -1,33 +1,22 @@ # =========================================================================== # adapter-service # -# One chart for the Beckn adapters, in two deployment modes. Same image and config -# format; `role` below decides the handler role, the step list and where -# requests are routed. +# One chart for all three Beckn adapters. provider, network and consumer are the +# same image and the same config format; `role` below decides the handler +# role, the step list and where requests are routed. # -# single (the DEFAULT): ONE release, the Network Adapter Layer, serving all -# three tiers as modules of one process (role network, -# networkAdapterDeploymentMode single): +# INSTALL ONCE PER ROLE, and keep the RELEASE name per-role: # -# helm install network-adapter . -n network-adapter --create-namespace \ -# -f examples/network-adapter.yaml +# helm install provider-adapter . -f examples/provider.yaml +# helm install network-adapter . -f examples/network.yaml +# helm install consumer-adapter . -f examples/consumer.yaml # -# multi (OPTIONAL): one release per tier, with networkAdapterDeploymentMode -# multi on the network release. INSTALL ONCE PER ROLE, and keep the RELEASE -# name per-role: -# -# helm install provider-adapter . -f examples/provider.yaml -# helm install network-adapter . -f examples/network.yaml -# helm install consumer-adapter . -f examples/consumer.yaml -# -# Run one deployment mode per environment. Both release `network-adapter` into -# namespace `network-adapter`; switching modes upgrades that release in place. +# Role network defaults to single mode, all three tiers in one release +# (examples/network-adapter.yaml); see networkAdapterDeploymentMode. # # The release name is what the Service is called, which is the DNS name the -# other adapters address. In multi, rename a release and you must update -# whatever routes to it -- consumer routes to the network adapter, provider is -# routed to by network. The single deployment mode has no such addresses: its tiers -# call each other inside the pod. +# other adapters address. Rename a release and you must update whatever routes +# to it -- consumer routes to the network adapter, provider is routed to by network. # # Two things every role needs that this chart cannot give it: # @@ -47,10 +36,8 @@ # --------------------------------------------------------------------------- role: "" -# single | multi. Read by role network only: single (the default) runs all -# three tiers in one adapter (networkAdapterLayer below); multi is the network -# tier alone. A multi network release MUST set multi, or an upgrade turns it -# into single. provider and consumer ignore it. +# single (default): role network runs all three tiers; multi: the network tier alone. +# A multi network release must set multi. provider and consumer ignore it. networkAdapterDeploymentMode: single # Overrides for what the role implies. Leave empty to take the role's default. @@ -347,11 +334,8 @@ resources: # Both hit /health, which the adapter serves from its own mux and which does # not touch the registry or discovery -- so a readiness failure here means # this process is unwell, not that something downstream is. -# The startup probe holds liveness off until /health first answers. The -# adapter fetches beckn.yaml from raw.githubusercontent.com at boot, so on -# slow or proxied egress first boot can take well past liveness's ~70s budget -# (10s + 3 x 20s). This bounds it at 180s without loosening steady-state -# liveness. +# The startup probe holds liveness off until /health answers; first boot fetches +# beckn.yaml, so it gets up to 180s. startupProbe: enabled: true httpGet: @@ -416,9 +400,8 @@ securityContext: # this release's selector labels when omitted. topologySpreadConstraints: [] -# Graceful shutdown. preStop sleeps so the endpoint leaves the Service before -# the process stops accepting. preStop.sleep needs Kubernetes 1.30+; keep 0 on -# older clusters. Empty grace period = the Kubernetes default (30s). +# Graceful shutdown: preStop sleep (Kubernetes 1.30+, keep 0 on older clusters). +# Empty grace period = the Kubernetes default (30s). terminationGracePeriodSeconds: "" preStopSleepSeconds: 0 @@ -440,32 +423,11 @@ extraVolumeMounts: [] extraEnv: [] envFromSecrets: [] -# --------------------------------------------------------------------------- -# role network, networkAdapterDeploymentMode single -- the Network Adapter Layer. -# -# The consumer, network and provider tiers as four modules of ONE process -# (/ for the consumer, /publish, /_internal/network/, /_internal/provider/). Its adapter config is -# config/network-adapter-config.yaml, mounted verbatim like any role's -# file: log level, timeouts, telemetry, extended schema and the outbound -# connection pool are set there. The routing is generated at render time from -# the tier routing files, with tier-to-tier hops pointed at this pod's own -# listener (http://localhost:/_internal/): any adapter image -# works. -# -# Ignored by provider, consumer and a multi network release. -# -# NOT USED in single mode: `routing.config` (setting it fails the render, because -# the routing is generated) and `handler.*` (role, steps and path are in the -# config file). `config` replaces config/network-adapter-config.yaml -# WHOLE: the private-key guard still runs on it, and it must point its routers -# at /app/config/routing-network-adapter-consumer.yaml, routing-network.yaml and -# routing-network-adapter-provider.yaml. -# --------------------------------------------------------------------------- +# Single mode only (role network): config is config/network-adapter-config.yaml, +# routing is generated; routing.config and handler.* are not used. networkAdapterLayer: - # Three identities, one per tier: the same Secrets (six keys each, named as - # under keys.existingSecret) and the same registry rows the multi-adapter - # releases use, so either mode runs against the same registry. They must - # exist in THIS release's namespace (openbao-secrets reflects them there). + # One Secret per tier (six keys each, as under keys.existingSecret), in this + # release's namespace; the same registry rows as the multi releases. keys: consumer: existingSecret: "" diff --git a/charts/openbao-secrets/CHANGELOG.md b/charts/openbao-secrets/CHANGELOG.md index 7e2160b0..9e3e163e 100644 --- a/charts/openbao-secrets/CHANGELOG.md +++ b/charts/openbao-secrets/CHANGELOG.md @@ -5,14 +5,6 @@ All notable changes to the `openbao-secrets` chart are documented here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.1.1] - 2026-10-09 - -### Added -- `network-adapter` as a target namespace of `consumer-adapter-keys`, - `provider-adapter-keys` and `provider-upstream`: in the single deployment - mode (the default) `adapter-service` role `network` runs all three tiers - there. In multi mode, remove `network-adapter` from those three entries. - ## [0.1.0] - 2026-09-27 ### Added diff --git a/charts/openbao-secrets/values.yaml b/charts/openbao-secrets/values.yaml index 24ee4686..8e7942a2 100644 --- a/charts/openbao-secrets/values.yaml +++ b/charts/openbao-secrets/values.yaml @@ -58,11 +58,8 @@ secrets: # resolve-key-ids.sh AFTER the registry has been seeded, so these entries # change once per environment after the first bring-up. # - # network-adapter runs the Network Adapter Layer in the single deployment - # mode (the default), which runs all three tiers, so it reads all three - # identities. In multi mode drop network-adapter from the consumer and - # provider entries, so the network tier never holds their keys - # (charts/adapter-service/README.md, "Multi mode"). + # network-adapter (single mode) reads all three identities and provider-upstream; + # in multi mode drop it from the consumer and provider entries (ADR-0001). - name: consumer-adapter-keys namespaces: [consumer-adapter, network-adapter] - name: network-adapter-keys @@ -71,8 +68,6 @@ secrets: namespaces: [provider-adapter, network-adapter] # The provider adapter's upstream credentials, named as environment variables - # by its config rather than substituted into it. The Network Adapter Layer - # (namespace network-adapter, single mode) runs the provider tier too, so it reads - # them as well. + # by its config rather than substituted into it. - name: provider-upstream namespaces: [provider-adapter, network-adapter] diff --git a/docs/adr/0000-template.md b/docs/adr/0000-template.md deleted file mode 100644 index 8751ab3f..00000000 --- a/docs/adr/0000-template.md +++ /dev/null @@ -1,24 +0,0 @@ -# ADR-0000 — Title - -**Status:** Proposed | Accepted | Superseded by ADR-NNNN -**Date:** YYYY-MM-DD -**Issues:** owner/repo#N - -## Context - -What forced a decision. The constraint, not the preamble. - -## Decision - -What was chosen, stated so that a reader can tell whether a given change -complies with it. - -## Alternatives considered - -What was rejected, and the specific property that disqualified it. "It was -worse" is not a reason; "it exposes an unsigned endpoint" is. - -## Consequences - -What this costs, including the cases it makes harder. An ADR that lists only -benefits is advocacy, not a record. diff --git a/docs/adr/0001-single-network-adapter-deployment-mode.md b/docs/adr/0001-single-network-adapter-deployment-mode.md deleted file mode 100644 index 02afdca8..00000000 --- a/docs/adr/0001-single-network-adapter-deployment-mode.md +++ /dev/null @@ -1,79 +0,0 @@ -# ADR-0001 — One Beckn OAN network adapter process for all three tiers, by default - -**Status:** Proposed -**Date:** 2026-10-15 -**Issues:** OpenAgriNet/helmcharts#55, OpenAgriNet/helmcharts#66, OpenAgriNet/network-adapter#55 (parent OpenAgriNet/engineering-tracker#171) - -## Context - -The Beckn OAN network adapter runs in three tiers: consumer (`bap`, 9202), network (`bpp`, 9201) and provider (`bpp`, 9200). Each was its own container in `quick-start/` and its own Helm release in `charts/adapter-service`, installed once per role. The release name is the Service name the other tiers address. - -So every request crossed two containers or pods, with a network hop and a DNS lookup: -- consumer to network for `discover`; -- consumer to provider for `select`; -- provider to network for `publish`. - -Every environment also ran three of every object: Deployment, Service, ConfigMap, and the PDB or HPA where enabled. - -OAN is single-sided today: our app calls out, and our own discovery and upstream APIs answer. The separate scaling and ownership the split allows is not needed. - -Constraints: -- **The registry cannot update a row.** The identities and participant ids already written must keep working. -- **The published Beckn OAN network adapter image must work unchanged.** - -## Decision - -Two deployment modes from the same chart, compose file, keys and registry rows. **`single` is the default; `multi` (the three adapters, unchanged) is optional.** - -In `single`, one Beckn OAN network adapter process listens on **9201** (the multi network tier's port) and mounts four modules. Each module is its tier's module from the tier configs, with the same steps and its own identity and Secret: - -| Module | Path | Steps | Routes to | -|---|---|---|---| -| consumer | `/` (app calls `/`) | validateSchema, addRoute, sign | `localhost:9201/_internal/network` (discover), `localhost:9201/_internal/provider` (select, init, confirm, status) | -| network | `/_internal/network/` | validateSign, addRoute | discovery | -| provider | `/_internal/provider/` | validateSign, validateSchema, capability steps | answers itself | -| provider-publish | `/publish` | validateSchema, addRoute, sign | `localhost:9201/_internal/network/publish` | - -A change complies with this decision if: -- **Paths:** the paths callers see stay `/discover`, `/publish`, `/` and `/health`, the same as multi. Tier-to-tier paths stay under `/_internal/`, called only by the process itself. -- **Routing:** each routing module keeps its own routing file (`routing-network-adapter-consumer.yaml`, `routing-network-adapter-provider.yaml`; network reuses `routing-network.yaml`). A module's routing file is its list of allowed actions: `addRoute` runs before `sign`, so an action with no rule errors instead of being forwarded. -- **Helm:** no new role. Role `network` with `networkAdapterDeploymentMode: single` (the default) is the single adapter; with `multi` it is the network tier. `provider` and `consumer` ignore the value. Both modes release `network-adapter` into namespace `network-adapter`. Routing is generated at render time from the existing `config/routing-*.yaml`, with hosts rewritten to `localhost`. The render fails on: - - a literal private key; - - `http.port` ≠ `service.targetPort`; - - an unset tier Secret name; - - an Ingress path other than single-segment `/`. -- **Secrets:** `openbao-secrets` reflects the consumer and provider key Secrets and `provider-upstream` into `network-adapter` for single mode. A multi environment removes those targets and deletes the copies, so the network tier holds only its own key. -- **Choosing the mode:** the chart value `networkAdapterDeploymentMode: single | multi` on the `network-adapter` release; multi also installs `consumer-adapter` and `provider-adapter`. In quick-start, `NETWORK_ADAPTER_DEPLOYMENT_MODE` picks `network-adapter`'s config, and profile `multi` adds the other two adapters, via `make up` and `make up-multi`. - -## Alternatives considered - -- **Keep three adapters only.** It keeps three of every object and a cross-pod hop on every request, for a separation a single-sided deployment does not use. -- **In-process transport (`inproc://`) between modules.** It needs an adapter image that includes the transport, so it fails the "image unchanged" constraint. The gain does not pay for that dependency: the `localhost` hop was already sub-millisecond on Docker, the prototype saved about 0.1–0.3 ms per hop, and an end-to-end benchmark showed no measurable gain. -- **Tier prefixes in the public paths** (`/network/`, `/provider/`, `/provider/publish`). This was the first version. It put the tiers back into the URLs callers use, gave single and multi different URLs, and needed a per-path allow-list at the edge instead of one `/_internal` deny. Replaced after review. -- **One Helm role `network` with a mode switch.** With `single` as the default, every existing `role: network` release would become a three-identity adapter on upgrade and fail without the consumer and provider Secrets. It also makes every existing network-tier code path depend on a new setting. -- **One shared routing file for the consumer and publish modules.** The publish module's rule is for the empty action `""`. Shared, it would also apply to the consumer, so `POST /` on the public consumer would publish into the catalogue. -- **Port 9200.** That is the multi provider's port. 9201 is the multi network tier's port and already the chart's default `service.port`. - -## Consequences - -**Gains:** -- One Deployment, Service, ConfigMap and PDB per environment. -- No hop between pods. -- No registry, identity or image change. -- Reversible: install the multi releases next to the single one, repoint the app, remove the single release. - -**Costs:** -- **Scaling is per adapter, not per tier.** An environment that needs a tier scaled on its own uses `multi`. -- **One crash takes all three tiers down.** Replicas (3 in the example), the PDB and the spread constraint limit the blast radius. -- **Tier configs exist twice.** Each module in `network-adapter-config.yaml` mirrors its tier file, so a change meant for both modes is made in both. -- **The catalogue publisher's port differs by mode:** `:9201/publish` in single, `:9200/publish` in multi. - -**Security:** -- `/publish` has no `validateSign`, and `/_internal/` is internal. Their protection is path-level: - - **Helm:** the Ingress guard; - - **quick-start:** the `server_proxy.conf` denies. -- Inside the cluster, anything that reaches port 9201 can call `/publish`. A NetworkPolicy cannot isolate it, because it has no notion of paths. Port-level isolation would need a second listener, which is not built. - -**Outside this repository:** `OpenAgriNet/network-adapter`'s own `install/` and `config/` still describe the per-role layout. Retiring them is the remaining part of network-adapter#55. - -**Verification (2026-10-22, flat paths, port 9201):** quick-start single (11/11 assertions) and multi (10/10), and k3d single (12/12), all passing: publish, discover and select 200, tier hops on `localhost:9201/_internal/`, and on k3d an unsigned call to `/_internal/network/discover` refused with 401. diff --git a/docs/adr/README.md b/docs/adr/README.md deleted file mode 100644 index e4ffadcc..00000000 --- a/docs/adr/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# Architecture Decision Records - -One file per decision, numbered, never edited after acceptance. A decision -that turns out wrong is **superseded** by a new record, not rewritten. The -point of the log is to answer *why does this look the way it does*, and a -rewritten record cannot answer it. - -Start from [`0000-template.md`](0000-template.md). - -| # | Decision | Issues | -|---|---|---| -| [0001](0001-single-network-adapter-deployment-mode.md) | One Beckn OAN network adapter process for all three tiers, by default (`single`); three adapters stay optional (`multi`) | helmcharts#55, #66 | From a48021c5872d71e0d981e4cb3f981c7df6ee6c49 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Thu, 22 Oct 2026 17:31:42 +0530 Subject: [PATCH 03/10] refactor(adapter-service): address review -- one mode value, simpler templates (#55, #66) - networkAdapterDeploymentMode is set only in values.yaml; examples and CI values no longer set it (multi installs pass --set ...=multi). ci/lint-values.yaml renders role provider. - Drop topologySpreadConstraints, terminationGracePeriodSeconds, preStopSleepSeconds and goMemLimit, and the scaling/PDB extras in the single-mode example and CI values. - Rename isNetworkAdapterLayer to singleMode; each template computes it once. - Chart description is one line; NOTES single-mode text cut down. - Restore the helpers removed earlier in _helpers.tpl. --- charts/adapter-service/Chart.yaml | 11 +--- charts/adapter-service/ci/lint-values.yaml | 7 +-- .../ci/network-adapter-values.yaml | 26 +------- .../examples/network-adapter.yaml | 24 -------- charts/adapter-service/examples/network.yaml | 5 +- charts/adapter-service/templates/NOTES.txt | 59 +++++-------------- charts/adapter-service/templates/_helpers.tpl | 56 +++++++++++++++++- .../templates/_network-adapter.tpl | 2 +- .../adapter-service/templates/configmap.yaml | 6 +- .../adapter-service/templates/deployment.yaml | 41 +++---------- charts/adapter-service/templates/ingress.yaml | 3 +- charts/adapter-service/values.yaml | 13 ---- 12 files changed, 91 insertions(+), 162 deletions(-) diff --git a/charts/adapter-service/Chart.yaml b/charts/adapter-service/Chart.yaml index b1d7bbb0..3210f813 100644 --- a/charts/adapter-service/Chart.yaml +++ b/charts/adapter-service/Chart.yaml @@ -1,15 +1,6 @@ apiVersion: v2 name: adapter-service -description: >- - One chart for the OAN Beckn adapters, in two deployment modes. single (the - default): the Network Adapter Layer (role network, networkAdapterDeploymentMode single) runs the - consumer, network and provider tiers as four modules of one process; - tier-to-tier calls stay inside the pod and each module keeps its own tier - identity. multi (optional): provider, network and consumer, one release per - tier. Same image and config format; role selects the handler role, the step - list and the routing target. Stateless: an adapter's - identity is a keypair held in a Secret, and everything else it reads from - the registry. +description: OAN Beckn adapter; role network runs all three tiers in single mode (default), or the network tier in multi. type: application version: 0.6.0 # The adapter binary, not this chart. `/health` reports the build it is diff --git a/charts/adapter-service/ci/lint-values.yaml b/charts/adapter-service/ci/lint-values.yaml index e4a060ee..4b6adcb9 100644 --- a/charts/adapter-service/ci/lint-values.yaml +++ b/charts/adapter-service/ci/lint-values.yaml @@ -3,9 +3,8 @@ # # role is first among them: it has no default, because a default would hand one # adapter another one's handler role and step list. -role: network -networkAdapterDeploymentMode: multi -fullnameOverride: network-adapter +role: provider +fullnameOverride: provider-adapter image: registry: ghcr.io @@ -14,7 +13,7 @@ image: keys: existingSecret: - name: network-adapter-keys + name: provider-adapter-keys registry: url: http://registry:8081/api/v1 diff --git a/charts/adapter-service/ci/network-adapter-values.yaml b/charts/adapter-service/ci/network-adapter-values.yaml index 78519a25..7f9e82ca 100644 --- a/charts/adapter-service/ci/network-adapter-values.yaml +++ b/charts/adapter-service/ci/network-adapter-values.yaml @@ -1,7 +1,6 @@ -# CI only: renders single mode (generated routing, tier identities, PDB, spread, -# shutdown) with ingress on and annotations null to exercise the path guard. +# CI only: renders single mode (generated routing, tier identities) +# with ingress on and annotations null to exercise the path guard. role: network -networkAdapterDeploymentMode: single fullnameOverride: network-adapter image: @@ -26,27 +25,6 @@ networkAdapterLayer: existingSecret: provider-adapter-keys extraSubstitutions: {} -# Scale defaults for the Network Adapter Layer: roughly the three tiers' -# requests in one pod, at least one replica per node. -resources: - requests: - cpu: 300m - memory: 384Mi - limits: - cpu: 1500m - memory: 1Gi -goMemLimit: 900MiB -replicaCount: 3 -podDisruptionBudget: - enabled: true - minAvailable: 1 -topologySpreadConstraints: - - maxSkew: 1 - topologyKey: kubernetes.io/hostname - whenUnsatisfiable: ScheduleAnyway -terminationGracePeriodSeconds: 40 -preStopSleepSeconds: 10 # needs Kubernetes 1.30+; 0 on older clusters - ingress: enabled: true className: nginx diff --git a/charts/adapter-service/examples/network-adapter.yaml b/charts/adapter-service/examples/network-adapter.yaml index 7fef92da..1f861d1d 100644 --- a/charts/adapter-service/examples/network-adapter.yaml +++ b/charts/adapter-service/examples/network-adapter.yaml @@ -1,7 +1,6 @@ # Single deployment mode (default): the consumer, network and provider tiers in # one release. Install: helm install network-adapter . -n network-adapter -f examples/network-adapter.yaml role: network -networkAdapterDeploymentMode: single # The Service name and DNS name callers use: # network-adapter.network-adapter.svc.cluster.local:9201 @@ -40,29 +39,6 @@ networkAdapterLayer: envFromSecrets: - provider-upstream -resources: - requests: - cpu: 300m - memory: 384Mi - limits: - cpu: 1500m - memory: 1Gi -goMemLimit: 900MiB - -replicaCount: 3 - -podDisruptionBudget: - enabled: true - minAvailable: 1 - -topologySpreadConstraints: - - maxSkew: 1 - topologyKey: kubernetes.io/hostname - whenUnsatisfiable: ScheduleAnyway - -terminationGracePeriodSeconds: 40 -preStopSleepSeconds: 10 # needs Kubernetes 1.30+; 0 on older clusters - # Off here. When on, only single-segment / paths (the consumer) are # accepted: /, /publish and /_internal* fail the render. ingress: diff --git a/charts/adapter-service/examples/network.yaml b/charts/adapter-service/examples/network.yaml index 5d4c15f9..1ceeca25 100644 --- a/charts/adapter-service/examples/network.yaml +++ b/charts/adapter-service/examples/network.yaml @@ -5,13 +5,12 @@ # publishes for them, hands discover and publish to the discovery service, and # signs on the way out. It answers neither action itself. # -# helm install network-adapter . -n network-adapter -f examples/network.yaml +# helm install network-adapter . -n network-adapter -f examples/network.yaml \ +# --set networkAdapterDeploymentMode=multi # # Keep the release name `network-adapter`: consumer routes to this Service by name. # =========================================================================== role: network -# Required for the network tier: the default, single, runs all three tiers. -networkAdapterDeploymentMode: multi # The Service name, and therefore the in-cluster DNS name other adapters use. # Set explicitly because fullname is otherwise "-" -- release diff --git a/charts/adapter-service/templates/NOTES.txt b/charts/adapter-service/templates/NOTES.txt index 151c8bbe..5da6470a 100644 --- a/charts/adapter-service/templates/NOTES.txt +++ b/charts/adapter-service/templates/NOTES.txt @@ -1,34 +1,25 @@ -{{- $nal := include "adapter-service.isNetworkAdapterLayer" . -}} +{{- $single := include "adapter-service.singleMode" . -}} {{ .Chart.Name }} {{ .Chart.Version }} — {{ include "adapter-service.fullname" . }} -{{ if $nal }} - mode single (the default): the Network Adapter Layer - role network, all three tiers in one adapter (/ consumer bap, /publish bap, /_internal/network/ bpp, /_internal/provider/ bpp) - hops between tiers inside the pod (http://localhost:{{ .Values.service.targetPort }}/_internal/) + +{{- if $single }} + + mode single: consumer, network and provider in one adapter (/, /publish, /_internal/*) {{- else }} - mode multi: one release per tier + role {{ include "adapter-service.role" . }} ({{ include "adapter-service.handlerRole" . }}) steps {{ include "adapter-service.steps" . | fromYamlArray | join " -> " }} {{- end }} image {{ include "adapter-service.image" . }} service {{ include "adapter-service.fullname" . }}:{{ .Values.service.port }} -{{- if $nal }} - registry set in config/network-adapter-config.yaml (registry.url is not used by this role) - routes generated from config/routing-{consumer,network,provider}.yaml - telemetry set in config/network-adapter-config.yaml (otel.* is not used by this role) -{{- else }} +{{- if not $single }} registry {{ .Values.registry.url }} routes from config/routing-{{ include "adapter-service.role" . }}.yaml telemetry {{ if .Values.otel.enabled }}{{ .Values.otel.endpoint }}{{ else }}off{{ end }} {{- end }} -This Service is reachable in-cluster as {{ include "adapter-service.fullname" . }}.{{ include "common.namespace" . }}.svc:{{ .Values.service.port }}. -{{- if $nal }} -The app and the catalogue publisher call it there; its tiers call each other -inside the pod, so no other adapter routes to it. -{{- else }} +This Service is reachable in-cluster as {{ include "adapter-service.fullname" . }}:{{ .Values.service.port }}. That name comes from the RELEASE name — whatever routes here must use it, so renaming this release means updating the adapter that points at it. -{{- end }} Check it is serving: @@ -40,35 +31,22 @@ chart's appVersion. TWO THINGS THIS CHART CANNOT CHECK FOR YOU -{{ if $nal -}} -1. The Participant rows. This adapter signs as three identities -- consumer, - network and provider -- from Secrets {{ include "adapter-service.networkAdapterLayer.tierSecret" (dict "ctx" . "tier" "consumer") }}, - {{ include "adapter-service.networkAdapterLayer.tierSecret" (dict "ctx" . "tier" "network") }} and {{ include "adapter-service.networkAdapterLayer.tierSecret" (dict "ctx" . "tier" "provider") }}: the same rows the - multi-adapter releases use. Each must carry its tier's public key. - -2. Key rotation. Changing any of those Secrets restarts nothing: - - kubectl -n {{ include "common.namespace" . }} rollout restart deploy/{{ include "adapter-service.fullname" . }} - -Callers address the tiers by path on this one Service: - app (discover, select, ...) {{ include "adapter-service.fullname" . }}:{{ .Values.service.port }}/ - catalogue publisher {{ include "adapter-service.fullname" . }}:{{ .Values.service.port }}/publish - /_internal/ is the network and provider tiers: never route to it from outside. +{{ if $single -}} +1. The Participant rows. Each tier signs from its own Secret + (networkAdapterLayer.keys); each registry row must carry that tier's public key. {{- else -}} 1. The Participant row. This adapter signs as {{ .Values.keys.existingSecret.name }}'s subscriberId, and every peer verifies that signature against the public key the registry publishes for it. If the row is missing, or carries a different key, this pod runs and reports Ready while every peer rejects what it signs — and the error appears in their logs, not yours. +{{- end }} -2. Key rotation. The keys live in Secret/{{ include "adapter-service.keysSecretName" . }}, - which this chart does not render and therefore cannot include in the config - checksum. Changing that Secret does not restart anything: +2. Key rotation. Changing a keys Secret does not restart anything: kubectl -n {{ include "common.namespace" . }} rollout restart deploy/{{ include "adapter-service.fullname" . }} -{{- end }} -{{- if and (not .Values.otel.enabled) (not $nal) }} +{{- if and (not .Values.otel.enabled) (not $single) }} Telemetry is off, which is the right setting until a collector exists: with it on and nothing listening the exporter logs a failure every interval. @@ -76,13 +54,8 @@ on and nothing listening the exporter logs a failure every interval. {{- if .Values.ingress.enabled }} Ingress is ON. -{{- if $nal }} -This is the single deployment mode. / (the consumer) accepts UNSIGNED requests: the consumer tier is -inside the trust boundary and has no validateSign step, so an Ingress on it exposes an -unauthenticated entry point to the network. Rate-limit this host. -Rule: only single-segment / paths may be exposed (the render refuses other paths), -never /_internal* or /publish: /_internal/ holds the network and provider tiers, and /publish -has no validateSign and signs as the provider. +{{- if $single }} +/ is the unsigned consumer: rate-limit this host. /publish and /_internal* are never exposed. {{- else if eq (include "adapter-service.role" .) "consumer" }} This is the consumer role, which accepts UNSIGNED requests — it is inside the trust boundary and has no validateSign step. An Ingress therefore exposes an diff --git a/charts/adapter-service/templates/_helpers.tpl b/charts/adapter-service/templates/_helpers.tpl index 377530b7..ccbf19ad 100644 --- a/charts/adapter-service/templates/_helpers.tpl +++ b/charts/adapter-service/templates/_helpers.tpl @@ -5,8 +5,9 @@ # Purpose: chart-local helpers delegating to common, plus the role, # identity, upstream and config-rendering wiring an adapter needs. # -# One chart, three roles: provider, network and consumer (role network runs all -# three tiers in single mode). The role decides handler role, steps and routing. Install once per role, and keep the RELEASE name +# One chart, three roles. provider, network and consumer are the same image and the +# same config format; the role decides the handler role, the step list and +# where requests are routed. Install once per role, and keep the RELEASE name # per-role -- the release name is what becomes the Service DNS name that the # other adapters address. # ============================================================================ @@ -22,7 +23,7 @@ and then mis-handles every request, at the far end, in a peer's logs. {{- define "adapter-service.role" -}} {{- $role := .Values.role | default "" -}} {{- if not $role -}} -{{- fail (printf "%s: role is required -- one of provider, network, consumer (role network runs all three tiers when networkAdapterDeploymentMode is single). It decides the handler role, the step list and the routing target, so there is no safe default. See examples/ for a values file per role." .Chart.Name) -}} +{{- fail (printf "%s: role is required -- one of provider, network, consumer. It decides the handler role, the step list and the routing target, so there is no safe default. See examples/ for a values file per role." .Chart.Name) -}} {{- end -}} {{- if not (has $role (list "provider" "network" "consumer")) -}} {{- fail (printf "%s: role must be one of provider, network, consumer (got %q)." .Chart.Name $role) -}} @@ -30,6 +31,22 @@ and then mis-handles every request, at the far end, in a peer's logs. {{- $role -}} {{- end }} +{{/* +The name the adapter calls itself in its config, its logs and its module. +Defaults to -adapter, matching the compose stack's service names. +*/}} +{{- define "adapter-service.appName" -}} +{{- .Values.appName | default (printf "%s-adapter" (include "adapter-service.role" .)) -}} +{{- end }} + +{{/* +OTEL service name. Defaults to oan--adapter, matching OTEL_SERVICE_NAME +in the compose stack so traces from either deployment line up. +*/}} +{{- define "adapter-service.otelServiceName" -}} +{{- .Values.otel.serviceName | default (printf "oan-%s-adapter" (include "adapter-service.role" .)) -}} +{{- end }} + {{/* bap originates a call, bpp receives one. consumer is the caller; network and provider receive. Overridable, because the role is a shorthand for a default @@ -70,6 +87,11 @@ self-describing when you exec into a pod. routing-{{ include "adapter-service.role" . }}.yaml {{- end }} + +{{- define "adapter-service.name" -}} +{{- include "common.name" . -}} +{{- end }} + {{- define "adapter-service.fullname" -}} {{- include "common.fullname" . -}} {{- end }} @@ -107,6 +129,34 @@ why. {{- $name -}} {{- end }} +{{/* +The registry API base. Required for the same reason as the keys: absent, the +adapter builds a plugin pointed at nothing and every signature verification +fails on a lookup rather than on the signature. +*/}} +{{- define "adapter-service.registryUrl" -}} +{{- $url := .Values.registry.url | default "" -}} +{{- if not $url -}} +{{- fail (printf "%s: registry.url is required -- e.g. http://registry:8081/api/v1. The adapter verifies every caller against the key the registry publishes for them, so with no registry it can verify nobody." .Chart.Name) -}} +{{- end -}} +{{- $url -}} +{{- end }} + +{{/* +OTLP endpoint, required only when telemetry is on. An enabled exporter with +no endpoint dials nothing and logs a failure per export interval, which is +the noise the three enable flags exist to avoid. +*/}} +{{- define "adapter-service.otlpEndpoint" -}} +{{- if .Values.otel.enabled -}} +{{- $ep := .Values.otel.endpoint | default "" -}} +{{- if not $ep -}} +{{- fail (printf "%s: otel.endpoint is required when otel.enabled is true -- e.g. otel-collector.observability.svc.cluster.local:4317. An enabled exporter with nowhere to send logs a failure every export interval." .Chart.Name) -}} +{{- end -}} +{{- $ep -}} +{{- end -}} +{{- end }} + {{/* Where the rendered config lands. An emptyDir, because the init container writes it and readOnlyRootFilesystem means there is nowhere else to write. diff --git a/charts/adapter-service/templates/_network-adapter.tpl b/charts/adapter-service/templates/_network-adapter.tpl index 9ea8da92..e3bf1c56 100644 --- a/charts/adapter-service/templates/_network-adapter.tpl +++ b/charts/adapter-service/templates/_network-adapter.tpl @@ -11,7 +11,7 @@ config/network-adapter-config.yaml, routing generated below (ADR-0001). */}} {{- end }} {{/* "true" for role network in single mode, else empty. */}} -{{- define "adapter-service.isNetworkAdapterLayer" -}} +{{- define "adapter-service.singleMode" -}} {{- if and (eq (include "adapter-service.role" .) "network") (eq (include "adapter-service.deploymentMode" .) "single") -}}true{{- end -}} {{- end }} diff --git a/charts/adapter-service/templates/configmap.yaml b/charts/adapter-service/templates/configmap.yaml index df057297..927ed2ac 100644 --- a/charts/adapter-service/templates/configmap.yaml +++ b/charts/adapter-service/templates/configmap.yaml @@ -27,9 +27,9 @@ at pod start, which is why no rendered config ever holds a key. A raw config with a literal private key in it would put that key in git, in a ConfigMap, and in `helm get values` -- so the render refuses it. */}} -{{- $networkAdapterLayer := include "adapter-service.isNetworkAdapterLayer" . }} +{{- $single := include "adapter-service.singleMode" . }} {{- $nlCfg := "" }} -{{- if $networkAdapterLayer }} +{{- if $single }} {{- /* Single mode: config/network-adapter-config.yaml unless `config` replaces it; private keys must be tier placeholders either way. */}} {{- $nlCfg = .Values.config | default (.Files.Get "config/network-adapter-config.yaml") }} @@ -66,7 +66,7 @@ metadata: {{- toYaml . | nindent 4 }} {{- end }} data: - {{- if $networkAdapterLayer }} + {{- if $single }} adapter.yaml: | {{- $nlCfg | nindent 4 }} {{- if .Values.routing.config }} diff --git a/charts/adapter-service/templates/deployment.yaml b/charts/adapter-service/templates/deployment.yaml index 6392ee3a..86414dcb 100644 --- a/charts/adapter-service/templates/deployment.yaml +++ b/charts/adapter-service/templates/deployment.yaml @@ -1,4 +1,5 @@ apiVersion: {{ include "common.deployment.apiVersion" . }} +{{- $single := include "adapter-service.singleMode" . }} kind: Deployment metadata: name: {{ include "adapter-service.fullname" . }} @@ -44,22 +45,6 @@ spec: securityContext: {{- . | nindent 8 }} {{- end }} - {{- with .Values.terminationGracePeriodSeconds }} - terminationGracePeriodSeconds: {{ . }} - {{- end }} - {{- with .Values.topologySpreadConstraints }} - topologySpreadConstraints: - {{- range . }} - - {{- toYaml (omit . "labelSelector") | nindent 10 }} - labelSelector: - {{- if .labelSelector }} - {{- toYaml .labelSelector | nindent 12 }} - {{- else }} - matchLabels: - {{- include "adapter-service.selectorLabels" $ | nindent 14 }} - {{- end }} - {{- end }} - {{- end }} initContainers: {{- include "common.waitFor" (dict "ctx" . "tcp" list "http" list) | nindent 8 }} {{/* @@ -86,7 +71,7 @@ spec: - -c - | set -eu - {{- if include "adapter-service.isNetworkAdapterLayer" . }} + {{- if $single }} # single mode: adapter.yaml plus one routing file per routed module. cp /config-template/*.yaml /rendered/ {{- else }} @@ -97,7 +82,7 @@ spec: subst() { # $1 placeholder, $2 file holding the value if [ ! -s "$2" ]; then - {{- if include "adapter-service.isNetworkAdapterLayer" . }} + {{- if $single }} # three tier Secrets share key names: name the tier too (e.g. network/keyId) echo "render-config: ${2#/keys/} is missing or empty in its tier's keys Secret" >&2 {{- else }} @@ -111,7 +96,7 @@ spec: # -- the sentinel contains a colon -- or an adapter signing with an # identity nothing can verify. if head -c 9 "$2" | grep -q "<&2 {{- else }} echo "render-config: $(basename "$2") is still unresolved." >&2 @@ -123,7 +108,7 @@ spec: fi sed -i "s|$1|$(cat "$2")|g" /rendered/adapter.yaml } - {{- if include "adapter-service.isNetworkAdapterLayer" . }} + {{- if $single }} {{- /* Each tier's module signs from its own Secret at /keys/. */}} {{- $k := .Values.keys.existingSecret }} @@ -164,12 +149,12 @@ spec: grep -oE '__[A-Z][A-Z0-9_]*__' /rendered/adapter.yaml | sort -u >&2 exit 1 fi - echo "render-config: config rendered for the {{ include "adapter-service.role" . }} role{{ if include "adapter-service.isNetworkAdapterLayer" . }} (single mode: all three tiers){{ end }}" + echo "render-config: config rendered for the {{ include "adapter-service.role" . }} role{{ if $single }} (single mode: all three tiers){{ end }}" volumeMounts: - name: config-template mountPath: /config-template readOnly: true - {{- if include "adapter-service.isNetworkAdapterLayer" . }} + {{- if $single }} {{- range $tier := list "consumer" "network" "provider" }} - name: keys-{{ $tier }} mountPath: /keys/{{ $tier }} @@ -191,12 +176,6 @@ spec: - name: {{ .Chart.Name }} image: {{ include "adapter-service.image" . }} imagePullPolicy: {{ .Values.image.pullPolicy }} - {{- if .Values.preStopSleepSeconds }} - lifecycle: - preStop: - sleep: - seconds: {{ .Values.preStopSleepSeconds }} - {{- end }} {{- with (include "common.securityContext" .) }} securityContext: {{- . | nindent 12 }} @@ -212,10 +191,6 @@ spec: */}} - name: CONFIG_FILE value: {{ include "adapter-service.configDir" . }}/adapter.yaml - {{- with .Values.goMemLimit }} - - name: GOMEMLIMIT - value: {{ . | quote }} - {{- end }} {{- with .Values.extraEnv }} {{- toYaml . | nindent 12 }} {{- end }} @@ -242,7 +217,7 @@ spec: - name: config-template configMap: name: {{ include "adapter-service.fullname" . }}-config - {{- if include "adapter-service.isNetworkAdapterLayer" . }} + {{- if $single }} {{- range $tier := list "consumer" "network" "provider" }} - name: keys-{{ $tier }} secret: diff --git a/charts/adapter-service/templates/ingress.yaml b/charts/adapter-service/templates/ingress.yaml index 0967750f..e30dfe92 100644 --- a/charts/adapter-service/templates/ingress.yaml +++ b/charts/adapter-service/templates/ingress.yaml @@ -1,6 +1,7 @@ {{- if .Values.ingress.enabled }} {{- $ann := merge (dict) (.Values.ingress.annotations | default dict) (.Values.commonAnnotations | default dict) }} -{{- if include "adapter-service.isNetworkAdapterLayer" . }} +{{- $single := include "adapter-service.singleMode" . }} +{{- if $single }} {{- /* Single mode: only literal one-segment / paths (Prefix/Exact, no regex or rewrite); never /, /publish or /_internal* (ADR-0001). */}} {{- if eq (index $ann "nginx.ingress.kubernetes.io/use-regex" | default "" | toString | trim | lower) "true" }} diff --git a/charts/adapter-service/values.yaml b/charts/adapter-service/values.yaml index 9cd47ad9..e9261cc8 100644 --- a/charts/adapter-service/values.yaml +++ b/charts/adapter-service/values.yaml @@ -396,19 +396,6 @@ securityContext: drop: - ALL -# Spread replicas so one node loss is not an outage. labelSelector defaults to -# this release's selector labels when omitted. -topologySpreadConstraints: [] - -# Graceful shutdown: preStop sleep (Kubernetes 1.30+, keep 0 on older clusters). -# Empty grace period = the Kubernetes default (30s). -terminationGracePeriodSeconds: "" -preStopSleepSeconds: 0 - -# Soft memory limit for the Go runtime (e.g. 900MiB under a 1Gi limit), so GC -# works harder before the container is OOM-killed. Empty = unset. -goMemLimit: "" - nodeSelector: {} tolerations: [] affinity: {} From caf9e149398e102140d8252a8c6146a0a3cfd8c1 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Thu, 22 Oct 2026 17:46:49 +0530 Subject: [PATCH 04/10] refactor(adapter-service): one mode helper, networkTierKeys with defaults (#55, #66) - adapter-service.deploymentMode is the only mode helper: the configured mode for role network, multi for provider and consumer. singleMode is removed. - networkAdapterLayer becomes networkTierKeys, defaulting to the Secret names openbao-secrets creates; the example and CI values no longer list them. - Helpers renamed without "layer": tierSecret, checkTierKeys, singleRouting. - ci/lint-values.yaml restored as on development; it renders single mode. --- charts/adapter-service/ci/lint-values.yaml | 6 ++--- .../ci/network-adapter-values.yaml | 10 -------- .../examples/network-adapter.yaml | 15 +++--------- charts/adapter-service/templates/NOTES.txt | 4 ++-- .../templates/_network-adapter.tpl | 23 ++++++++----------- .../adapter-service/templates/configmap.yaml | 6 ++--- .../adapter-service/templates/deployment.yaml | 6 ++--- charts/adapter-service/templates/ingress.yaml | 2 +- charts/adapter-service/values.yaml | 21 ++++++----------- 9 files changed, 31 insertions(+), 62 deletions(-) diff --git a/charts/adapter-service/ci/lint-values.yaml b/charts/adapter-service/ci/lint-values.yaml index 4b6adcb9..d01cdfad 100644 --- a/charts/adapter-service/ci/lint-values.yaml +++ b/charts/adapter-service/ci/lint-values.yaml @@ -3,8 +3,8 @@ # # role is first among them: it has no default, because a default would hand one # adapter another one's handler role and step list. -role: provider -fullnameOverride: provider-adapter +role: network +fullnameOverride: network-adapter image: registry: ghcr.io @@ -13,7 +13,7 @@ image: keys: existingSecret: - name: provider-adapter-keys + name: network-adapter-keys registry: url: http://registry:8081/api/v1 diff --git a/charts/adapter-service/ci/network-adapter-values.yaml b/charts/adapter-service/ci/network-adapter-values.yaml index 7f9e82ca..91f4744c 100644 --- a/charts/adapter-service/ci/network-adapter-values.yaml +++ b/charts/adapter-service/ci/network-adapter-values.yaml @@ -15,16 +15,6 @@ service: port: 9201 targetPort: 9201 -networkAdapterLayer: - keys: - consumer: - existingSecret: consumer-adapter-keys - network: - existingSecret: network-adapter-keys - provider: - existingSecret: provider-adapter-keys - extraSubstitutions: {} - ingress: enabled: true className: nginx diff --git a/charts/adapter-service/examples/network-adapter.yaml b/charts/adapter-service/examples/network-adapter.yaml index 1f861d1d..292c5c07 100644 --- a/charts/adapter-service/examples/network-adapter.yaml +++ b/charts/adapter-service/examples/network-adapter.yaml @@ -20,18 +20,9 @@ service: port: 9201 targetPort: 9201 -networkAdapterLayer: - # The three tier Secrets the multi-adapter releases use. openbao-secrets - # reflects them into this namespace. - keys: - consumer: - existingSecret: consumer-adapter-keys - network: - existingSecret: network-adapter-keys - provider: - existingSecret: provider-adapter-keys - # Set per environment, e.g. __VISTAAR_TOKEN_URL__: provider/vistaarTokenUrl, - # or the init container stops on the leftover placeholder. +# Tier key Secrets default to consumer-, network- and provider-adapter-keys. +# Set per environment, or the init container stops on the leftover placeholder. +networkTierKeys: extraSubstitutions: {} # The provider tier reads its upstream credentials (MAUSAMGRAM_AUTH, diff --git a/charts/adapter-service/templates/NOTES.txt b/charts/adapter-service/templates/NOTES.txt index 5da6470a..7ab13956 100644 --- a/charts/adapter-service/templates/NOTES.txt +++ b/charts/adapter-service/templates/NOTES.txt @@ -1,4 +1,4 @@ -{{- $single := include "adapter-service.singleMode" . -}} +{{- $single := eq (include "adapter-service.deploymentMode" .) "single" -}} {{ .Chart.Name }} {{ .Chart.Version }} — {{ include "adapter-service.fullname" . }} {{- if $single }} @@ -33,7 +33,7 @@ TWO THINGS THIS CHART CANNOT CHECK FOR YOU {{ if $single -}} 1. The Participant rows. Each tier signs from its own Secret - (networkAdapterLayer.keys); each registry row must carry that tier's public key. + (networkTierKeys); each registry row must carry that tier's public key. {{- else -}} 1. The Participant row. This adapter signs as {{ .Values.keys.existingSecret.name }}'s subscriberId, and every peer verifies that signature against the public key diff --git a/charts/adapter-service/templates/_network-adapter.tpl b/charts/adapter-service/templates/_network-adapter.tpl index e3bf1c56..68a855ec 100644 --- a/charts/adapter-service/templates/_network-adapter.tpl +++ b/charts/adapter-service/templates/_network-adapter.tpl @@ -1,23 +1,18 @@ {{/* Single mode (role network): three tiers in one adapter; config from config/network-adapter-config.yaml, routing generated below (ADR-0001). */}} -{{/* networkAdapterDeploymentMode: single (default) | multi; only role network reads it. */}} +{{/* networkAdapterDeploymentMode (single | multi); provider and consumer are always multi. */}} {{- define "adapter-service.deploymentMode" -}} {{- $mode := .Values.networkAdapterDeploymentMode | default "single" -}} {{- if not (has $mode (list "single" "multi")) -}} {{- fail (printf "%s: networkAdapterDeploymentMode must be single or multi (got %q)." .Chart.Name $mode) -}} {{- end -}} -{{- $mode -}} -{{- end }} - -{{/* "true" for role network in single mode, else empty. */}} -{{- define "adapter-service.singleMode" -}} -{{- if and (eq (include "adapter-service.role" .) "network") (eq (include "adapter-service.deploymentMode" .) "single") -}}true{{- end -}} +{{- ternary $mode "multi" (eq (include "adapter-service.role" .) "network") -}} {{- end }} {{/* Fails unless every module's private keys are tier placeholders. -Usage: include "adapter-service.networkAdapterLayer.checkKeys" (dict "ctx" $ "cfg" $parsedConfig) */}} -{{- define "adapter-service.networkAdapterLayer.checkKeys" -}} +Usage: include "adapter-service.checkTierKeys" (dict "ctx" $ "cfg" $parsedConfig) */}} +{{- define "adapter-service.checkTierKeys" -}} {{- range $m := .cfg.modules -}} {{- $km := dig "handler" "plugins" "keyManager" "config" dict $m -}} {{- range $field := list "signingPrivateKey" "encrPrivateKey" -}} @@ -31,7 +26,7 @@ Usage: include "adapter-service.networkAdapterLayer.checkKeys" (dict "ctx" $ "cf {{/* Single-mode routing as {: }: tier hosts in routing-{consumer,provider}.yaml become http://localhost:/_internal/ (ADR-0001); routing-network.yaml as is. */}} -{{- define "adapter-service.networkAdapterLayer.routing" -}} +{{- define "adapter-service.singleRouting" -}} {{- $ctx := . -}} {{- $ownListener := printf "http://localhost:%v/_internal/${1}${4}" .Values.service.targetPort -}} {{- /* Anchored: only a tier host (optional namespace and port) plus optional path. */ -}} @@ -59,11 +54,11 @@ become http://localhost:/_internal/ (ADR-0001); routing-networ {{- end }} {{/* One tier's key Secret; fails naming the tier when unset. -Usage: include "adapter-service.networkAdapterLayer.tierSecret" (dict "ctx" $ "tier" "network") */}} -{{- define "adapter-service.networkAdapterLayer.tierSecret" -}} -{{- $name := (index .ctx.Values.networkAdapterLayer.keys .tier).existingSecret | default "" -}} +Usage: include "adapter-service.tierSecret" (dict "ctx" $ "tier" "network") */}} +{{- define "adapter-service.tierSecret" -}} +{{- $name := index .ctx.Values.networkTierKeys .tier | default "" -}} {{- if not $name -}} -{{- fail (printf "%s: networkAdapterLayer.keys.%s.existingSecret is required -- the %s tier signs as its own identity, from its own Secret (the six keys under keys.existingSecret)." .ctx.Chart.Name .tier .tier) -}} +{{- fail (printf "%s: networkTierKeys.%s is required -- the %s tier signs as its own identity, from its own Secret (the six keys under keys.existingSecret)." .ctx.Chart.Name .tier .tier) -}} {{- end -}} {{- $name -}} {{- end }} diff --git a/charts/adapter-service/templates/configmap.yaml b/charts/adapter-service/templates/configmap.yaml index 927ed2ac..6cb524cc 100644 --- a/charts/adapter-service/templates/configmap.yaml +++ b/charts/adapter-service/templates/configmap.yaml @@ -27,7 +27,7 @@ at pod start, which is why no rendered config ever holds a key. A raw config with a literal private key in it would put that key in git, in a ConfigMap, and in `helm get values` -- so the render refuses it. */}} -{{- $single := include "adapter-service.singleMode" . }} +{{- $single := eq (include "adapter-service.deploymentMode" .) "single" }} {{- $nlCfg := "" }} {{- if $single }} {{- /* Single mode: config/network-adapter-config.yaml unless `config` replaces it; @@ -40,7 +40,7 @@ in `helm get values` -- so the render refuses it. {{- if or (not $nlParsed) (hasKey $nlParsed "Error") }} {{- fail (printf "%s: the single-mode adapter config (`config` override or config/network-adapter-config.yaml) does not parse as YAML, so its private keys cannot be checked." .Chart.Name) }} {{- end }} -{{- include "adapter-service.networkAdapterLayer.checkKeys" (dict "ctx" . "cfg" $nlParsed) }} +{{- include "adapter-service.checkTierKeys" (dict "ctx" . "cfg" $nlParsed) }} {{- if ne (toString (dig "http" "port" "" $nlParsed)) (toString .Values.service.targetPort) }} {{- fail (printf "%s: the single-mode config listens on http.port %v but service.targetPort is %v. The generated routing sends tier hops to localhost:, so the two must match." .Chart.Name (dig "http" "port" "unset" $nlParsed) .Values.service.targetPort) }} {{- end }} @@ -72,7 +72,7 @@ data: {{- if .Values.routing.config }} {{- fail (printf "%s: routing.config is not used in the single deployment mode. Its routing files are generated from config/routing-{consumer,network,provider}.yaml. To change a route edit those files; to replace the whole adapter config set `config`." .Chart.Name) }} {{- end }} - {{- range $file, $doc := include "adapter-service.networkAdapterLayer.routing" . | fromYaml }} + {{- range $file, $doc := include "adapter-service.singleRouting" . | fromYaml }} {{ $file }}: | # GENERATED by adapter-service from config/routing-*.yaml. Do not edit; edit those files. {{- toYaml $doc | nindent 4 }} diff --git a/charts/adapter-service/templates/deployment.yaml b/charts/adapter-service/templates/deployment.yaml index 86414dcb..bc4960f4 100644 --- a/charts/adapter-service/templates/deployment.yaml +++ b/charts/adapter-service/templates/deployment.yaml @@ -1,5 +1,5 @@ apiVersion: {{ include "common.deployment.apiVersion" . }} -{{- $single := include "adapter-service.singleMode" . }} +{{- $single := eq (include "adapter-service.deploymentMode" .) "single" }} kind: Deployment metadata: name: {{ include "adapter-service.fullname" . }} @@ -121,7 +121,7 @@ spec: subst __{{ $T }}_ENCR_PRIVATE__ /keys/{{ $tier }}/{{ $k.encrPrivateKey }} subst __{{ $T }}_ENCR_PUBLIC__ /keys/{{ $tier }}/{{ $k.encrPublicKey }} {{- end }} - {{- range $ph, $path := .Values.networkAdapterLayer.extraSubstitutions }} + {{- range $ph, $path := .Values.networkTierKeys.extraSubstitutions }} subst {{ $ph }} /keys/{{ $path }} {{- end }} {{- else }} @@ -221,7 +221,7 @@ spec: {{- range $tier := list "consumer" "network" "provider" }} - name: keys-{{ $tier }} secret: - secretName: {{ include "adapter-service.networkAdapterLayer.tierSecret" (dict "ctx" $ "tier" $tier) }} + secretName: {{ include "adapter-service.tierSecret" (dict "ctx" $ "tier" $tier) }} defaultMode: 0400 {{- end }} {{- else }} diff --git a/charts/adapter-service/templates/ingress.yaml b/charts/adapter-service/templates/ingress.yaml index e30dfe92..02daa17b 100644 --- a/charts/adapter-service/templates/ingress.yaml +++ b/charts/adapter-service/templates/ingress.yaml @@ -1,6 +1,6 @@ {{- if .Values.ingress.enabled }} {{- $ann := merge (dict) (.Values.ingress.annotations | default dict) (.Values.commonAnnotations | default dict) }} -{{- $single := include "adapter-service.singleMode" . }} +{{- $single := eq (include "adapter-service.deploymentMode" .) "single" }} {{- if $single }} {{- /* Single mode: only literal one-segment / paths (Prefix/Exact, no regex or rewrite); never /, /publish or /_internal* (ADR-0001). */}} diff --git a/charts/adapter-service/values.yaml b/charts/adapter-service/values.yaml index e9261cc8..96d7880c 100644 --- a/charts/adapter-service/values.yaml +++ b/charts/adapter-service/values.yaml @@ -410,19 +410,12 @@ extraVolumeMounts: [] extraEnv: [] envFromSecrets: [] -# Single mode only (role network): config is config/network-adapter-config.yaml, -# routing is generated; routing.config and handler.* are not used. -networkAdapterLayer: - # One Secret per tier (six keys each, as under keys.existingSecret), in this - # release's namespace; the same registry rows as the multi releases. - keys: - consumer: - existingSecret: "" - network: - existingSecret: "" - provider: - existingSecret: "" - - # Extra placeholders, as "/". +# Single mode only: one key Secret per tier (six keys each, as under keys.existingSecret), +# named as openbao-secrets creates them. routing.config and handler.* are unused. +networkTierKeys: + consumer: consumer-adapter-keys + network: network-adapter-keys + provider: provider-adapter-keys + # Extra placeholders, as "/", e.g. # __VISTAAR_TOKEN_URL__: provider/vistaarTokenUrl extraSubstitutions: {} From 922c3a1085cc8d964733c048b66ad9c186a5da7a Mon Sep 17 00:00:00 2001 From: Kelvin Date: Thu, 22 Oct 2026 17:53:28 +0530 Subject: [PATCH 05/10] refactor(adapter-service): prefix network tier helpers with network (#55, #66) tierSecret, checkTierKeys and singleRouting become networkTierSecret, networkCheckTierKeys and networkSingleRouting. --- charts/adapter-service/templates/_network-adapter.tpl | 10 +++++----- charts/adapter-service/templates/configmap.yaml | 4 ++-- charts/adapter-service/templates/deployment.yaml | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/charts/adapter-service/templates/_network-adapter.tpl b/charts/adapter-service/templates/_network-adapter.tpl index 68a855ec..0fadbbf5 100644 --- a/charts/adapter-service/templates/_network-adapter.tpl +++ b/charts/adapter-service/templates/_network-adapter.tpl @@ -11,8 +11,8 @@ config/network-adapter-config.yaml, routing generated below (ADR-0001). */}} {{- end }} {{/* Fails unless every module's private keys are tier placeholders. -Usage: include "adapter-service.checkTierKeys" (dict "ctx" $ "cfg" $parsedConfig) */}} -{{- define "adapter-service.checkTierKeys" -}} +Usage: include "adapter-service.networkCheckTierKeys" (dict "ctx" $ "cfg" $parsedConfig) */}} +{{- define "adapter-service.networkCheckTierKeys" -}} {{- range $m := .cfg.modules -}} {{- $km := dig "handler" "plugins" "keyManager" "config" dict $m -}} {{- range $field := list "signingPrivateKey" "encrPrivateKey" -}} @@ -26,7 +26,7 @@ Usage: include "adapter-service.checkTierKeys" (dict "ctx" $ "cfg" $parsedConfig {{/* Single-mode routing as {: }: tier hosts in routing-{consumer,provider}.yaml become http://localhost:/_internal/ (ADR-0001); routing-network.yaml as is. */}} -{{- define "adapter-service.singleRouting" -}} +{{- define "adapter-service.networkSingleRouting" -}} {{- $ctx := . -}} {{- $ownListener := printf "http://localhost:%v/_internal/${1}${4}" .Values.service.targetPort -}} {{- /* Anchored: only a tier host (optional namespace and port) plus optional path. */ -}} @@ -54,8 +54,8 @@ become http://localhost:/_internal/ (ADR-0001); routing-networ {{- end }} {{/* One tier's key Secret; fails naming the tier when unset. -Usage: include "adapter-service.tierSecret" (dict "ctx" $ "tier" "network") */}} -{{- define "adapter-service.tierSecret" -}} +Usage: include "adapter-service.networkTierSecret" (dict "ctx" $ "tier" "network") */}} +{{- define "adapter-service.networkTierSecret" -}} {{- $name := index .ctx.Values.networkTierKeys .tier | default "" -}} {{- if not $name -}} {{- fail (printf "%s: networkTierKeys.%s is required -- the %s tier signs as its own identity, from its own Secret (the six keys under keys.existingSecret)." .ctx.Chart.Name .tier .tier) -}} diff --git a/charts/adapter-service/templates/configmap.yaml b/charts/adapter-service/templates/configmap.yaml index 6cb524cc..0d5f4c77 100644 --- a/charts/adapter-service/templates/configmap.yaml +++ b/charts/adapter-service/templates/configmap.yaml @@ -40,7 +40,7 @@ in `helm get values` -- so the render refuses it. {{- if or (not $nlParsed) (hasKey $nlParsed "Error") }} {{- fail (printf "%s: the single-mode adapter config (`config` override or config/network-adapter-config.yaml) does not parse as YAML, so its private keys cannot be checked." .Chart.Name) }} {{- end }} -{{- include "adapter-service.checkTierKeys" (dict "ctx" . "cfg" $nlParsed) }} +{{- include "adapter-service.networkCheckTierKeys" (dict "ctx" . "cfg" $nlParsed) }} {{- if ne (toString (dig "http" "port" "" $nlParsed)) (toString .Values.service.targetPort) }} {{- fail (printf "%s: the single-mode config listens on http.port %v but service.targetPort is %v. The generated routing sends tier hops to localhost:, so the two must match." .Chart.Name (dig "http" "port" "unset" $nlParsed) .Values.service.targetPort) }} {{- end }} @@ -72,7 +72,7 @@ data: {{- if .Values.routing.config }} {{- fail (printf "%s: routing.config is not used in the single deployment mode. Its routing files are generated from config/routing-{consumer,network,provider}.yaml. To change a route edit those files; to replace the whole adapter config set `config`." .Chart.Name) }} {{- end }} - {{- range $file, $doc := include "adapter-service.singleRouting" . | fromYaml }} + {{- range $file, $doc := include "adapter-service.networkSingleRouting" . | fromYaml }} {{ $file }}: | # GENERATED by adapter-service from config/routing-*.yaml. Do not edit; edit those files. {{- toYaml $doc | nindent 4 }} diff --git a/charts/adapter-service/templates/deployment.yaml b/charts/adapter-service/templates/deployment.yaml index bc4960f4..7287c481 100644 --- a/charts/adapter-service/templates/deployment.yaml +++ b/charts/adapter-service/templates/deployment.yaml @@ -221,7 +221,7 @@ spec: {{- range $tier := list "consumer" "network" "provider" }} - name: keys-{{ $tier }} secret: - secretName: {{ include "adapter-service.tierSecret" (dict "ctx" $ "tier" $tier) }} + secretName: {{ include "adapter-service.networkTierSecret" (dict "ctx" $ "tier" $tier) }} defaultMode: 0400 {{- end }} {{- else }} From d3a182113d20ec011d7faa327142a0b615b05e8a Mon Sep 17 00:00:00 2001 From: Kelvin Date: Fri, 23 Oct 2026 13:24:37 +0530 Subject: [PATCH 06/10] refactor(adapter-service): one network example; the mode alone switches (#55, #66) In single mode the chart adds networkTierKeys.upstreamSecret (default provider-upstream) to envFrom itself, so examples/network.yaml serves both modes and switching is only: --set networkAdapterDeploymentMode=single|multi - templates/deployment.yaml: append the upstream Secret to envFrom in single mode. - values.yaml: networkTierKeys.upstreamSecret. - examples/network-adapter.yaml removed; its single render is identical to examples/network.yaml with the default mode. Refs: #55, #66, OpenAgriNet/network-adapter#55, OpenAgriNet/engineering-tracker#171 --- .../examples/network-adapter.yaml | 53 ------------------- charts/adapter-service/examples/network.yaml | 5 +- .../adapter-service/templates/deployment.yaml | 6 ++- charts/adapter-service/values.yaml | 4 +- 4 files changed, 11 insertions(+), 57 deletions(-) delete mode 100644 charts/adapter-service/examples/network-adapter.yaml diff --git a/charts/adapter-service/examples/network-adapter.yaml b/charts/adapter-service/examples/network-adapter.yaml deleted file mode 100644 index 292c5c07..00000000 --- a/charts/adapter-service/examples/network-adapter.yaml +++ /dev/null @@ -1,53 +0,0 @@ -# Single deployment mode (default): the consumer, network and provider tiers in -# one release. Install: helm install network-adapter . -n network-adapter -f examples/network-adapter.yaml -role: network - -# The Service name and DNS name callers use: -# network-adapter.network-adapter.svc.cluster.local:9201 -fullnameOverride: network-adapter - -image: - registry: ghcr.io - repository: openagrinet/network-adapter - tag: "latest" - -# registry.url, otel.*, appName, logLevel, http.timeout.*, routing.* and handler.* -# are unused in single mode: set them in config/network-adapter-config.yaml. - -# targetPort must equal http.port in config/network-adapter-config.yaml (the -# render checks); the generated routing uses it. -service: - port: 9201 - targetPort: 9201 - -# Tier key Secrets default to consumer-, network- and provider-adapter-keys. -# Set per environment, or the init container stops on the leftover placeholder. -networkTierKeys: - extraSubstitutions: {} - -# The provider tier reads its upstream credentials (MAUSAMGRAM_AUTH, -# AGMARKNET_*, VISTAAR_CLIENT_*) from the environment. -envFromSecrets: - - provider-upstream - -# Off here. When on, only single-segment / paths (the consumer) are -# accepted: /, /publish and /_internal* fail the render. -ingress: - enabled: false - className: "" - hosts: - - host: network-adapter.oan.example.com - paths: - - path: /discover - pathType: Exact - - path: /select - pathType: Exact - - path: /init - pathType: Exact - - path: /confirm - pathType: Exact - - path: /status - pathType: Exact - -waitFor: - enabled: false diff --git a/charts/adapter-service/examples/network.yaml b/charts/adapter-service/examples/network.yaml index 1ceeca25..df93f532 100644 --- a/charts/adapter-service/examples/network.yaml +++ b/charts/adapter-service/examples/network.yaml @@ -1,12 +1,13 @@ # =========================================================================== -# network role, multi deployment mode -- the network tier alone. +# network role. networkAdapterDeploymentMode decides: single (default) runs all +# three tiers; multi runs the network tier alone. # # Verifies that the caller's signature checks out against the key the registry # publishes for them, hands discover and publish to the discovery service, and # signs on the way out. It answers neither action itself. # # helm install network-adapter . -n network-adapter -f examples/network.yaml \ -# --set networkAdapterDeploymentMode=multi +# --set networkAdapterDeploymentMode=single # or multi # # Keep the release name `network-adapter`: consumer routes to this Service by name. # =========================================================================== diff --git a/charts/adapter-service/templates/deployment.yaml b/charts/adapter-service/templates/deployment.yaml index 7287c481..e361b096 100644 --- a/charts/adapter-service/templates/deployment.yaml +++ b/charts/adapter-service/templates/deployment.yaml @@ -194,7 +194,11 @@ spec: {{- with .Values.extraEnv }} {{- toYaml . | nindent 12 }} {{- end }} - {{- with .Values.envFromSecrets }} + {{- $envFrom := .Values.envFromSecrets | default list }} + {{- if and $single .Values.networkTierKeys.upstreamSecret }} + {{- $envFrom = append $envFrom .Values.networkTierKeys.upstreamSecret }} + {{- end }} + {{- with $envFrom }} envFrom: {{- range . }} - secretRef: diff --git a/charts/adapter-service/values.yaml b/charts/adapter-service/values.yaml index 96d7880c..5e5f4447 100644 --- a/charts/adapter-service/values.yaml +++ b/charts/adapter-service/values.yaml @@ -12,7 +12,7 @@ # helm install consumer-adapter . -f examples/consumer.yaml # # Role network defaults to single mode, all three tiers in one release -# (examples/network-adapter.yaml); see networkAdapterDeploymentMode. +# (examples/network.yaml); see networkAdapterDeploymentMode. # # The release name is what the Service is called, which is the DNS name the # other adapters address. Rename a release and you must update whatever routes @@ -416,6 +416,8 @@ networkTierKeys: consumer: consumer-adapter-keys network: network-adapter-keys provider: provider-adapter-keys + # The provider tier's upstream credentials, loaded as env vars in single mode. + upstreamSecret: provider-upstream # Extra placeholders, as "/", e.g. # __VISTAAR_TOKEN_URL__: provider/vistaarTokenUrl extraSubstitutions: {} From edf15db503de69daab16d89c540fad6a6a303d2e Mon Sep 17 00:00:00 2001 From: Kelvin Date: Fri, 23 Oct 2026 17:06:20 +0530 Subject: [PATCH 07/10] refactor(adapter-service): adapter naming, deploymentMode, config subfolders (#55, #66) Addresses review 5627183696: - Roles are consumer-adapter, network-adapter and provider-adapter. The old names consumer, network and provider are still accepted and render the same. - networkAdapterDeploymentMode becomes deploymentMode: single-adapter (default) | multi-adapter. - networkTierKeys becomes adapterKeys, keyed consumer-adapter, network-adapter, provider-adapter (plus upstreamSecret, extraSubstitutions). - Single config modules: consumer-adapter, network-adapter, oanProvider, oanProviderPublish (the names the multi configs already use). - Configs move to config/single-adapter/ and config/multi-adapter/. - Examples and CI values use the new role names. Multi, standalone consumer/provider and otel CI renders are unchanged; single differs only in module names and comment text. Refs: #55, #66, OpenAgriNet/network-adapter#55, OpenAgriNet/engineering-tracker#171 --- .../config/{ => multi-adapter}/consumer-config.yaml | 0 .../config/{ => multi-adapter}/network-config.yaml | 0 .../config/{ => multi-adapter}/provider-config.yaml | 0 .../config/{ => multi-adapter}/routing-consumer.yaml | 0 .../config/{ => multi-adapter}/routing-network.yaml | 0 .../config/{ => multi-adapter}/routing-provider.yaml | 0 .../config/{ => single-adapter}/network-adapter-config.yaml | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename charts/adapter-service/config/{ => multi-adapter}/consumer-config.yaml (100%) rename charts/adapter-service/config/{ => multi-adapter}/network-config.yaml (100%) rename charts/adapter-service/config/{ => multi-adapter}/provider-config.yaml (100%) rename charts/adapter-service/config/{ => multi-adapter}/routing-consumer.yaml (100%) rename charts/adapter-service/config/{ => multi-adapter}/routing-network.yaml (100%) rename charts/adapter-service/config/{ => multi-adapter}/routing-provider.yaml (100%) rename charts/adapter-service/config/{ => single-adapter}/network-adapter-config.yaml (100%) diff --git a/charts/adapter-service/config/consumer-config.yaml b/charts/adapter-service/config/multi-adapter/consumer-config.yaml similarity index 100% rename from charts/adapter-service/config/consumer-config.yaml rename to charts/adapter-service/config/multi-adapter/consumer-config.yaml diff --git a/charts/adapter-service/config/network-config.yaml b/charts/adapter-service/config/multi-adapter/network-config.yaml similarity index 100% rename from charts/adapter-service/config/network-config.yaml rename to charts/adapter-service/config/multi-adapter/network-config.yaml diff --git a/charts/adapter-service/config/provider-config.yaml b/charts/adapter-service/config/multi-adapter/provider-config.yaml similarity index 100% rename from charts/adapter-service/config/provider-config.yaml rename to charts/adapter-service/config/multi-adapter/provider-config.yaml diff --git a/charts/adapter-service/config/routing-consumer.yaml b/charts/adapter-service/config/multi-adapter/routing-consumer.yaml similarity index 100% rename from charts/adapter-service/config/routing-consumer.yaml rename to charts/adapter-service/config/multi-adapter/routing-consumer.yaml diff --git a/charts/adapter-service/config/routing-network.yaml b/charts/adapter-service/config/multi-adapter/routing-network.yaml similarity index 100% rename from charts/adapter-service/config/routing-network.yaml rename to charts/adapter-service/config/multi-adapter/routing-network.yaml diff --git a/charts/adapter-service/config/routing-provider.yaml b/charts/adapter-service/config/multi-adapter/routing-provider.yaml similarity index 100% rename from charts/adapter-service/config/routing-provider.yaml rename to charts/adapter-service/config/multi-adapter/routing-provider.yaml diff --git a/charts/adapter-service/config/network-adapter-config.yaml b/charts/adapter-service/config/single-adapter/network-adapter-config.yaml similarity index 100% rename from charts/adapter-service/config/network-adapter-config.yaml rename to charts/adapter-service/config/single-adapter/network-adapter-config.yaml From cacb5c9451a717ebb224c8ba77aadae13fb04f2f Mon Sep 17 00:00:00 2001 From: Kelvin Date: Fri, 23 Oct 2026 17:07:30 +0530 Subject: [PATCH 08/10] refactor(adapter-service): content of the adapter naming change (#55, #66) The previous commit (edf15db) pushed only the moves into config/single-adapter/ and config/multi-adapter/. This adds the template, values, example and CI changes it describes, so the chart renders again. Refs: #55, #66, OpenAgriNet/network-adapter#55, OpenAgriNet/engineering-tracker#171 --- charts/adapter-service/Chart.yaml | 2 +- charts/adapter-service/ci/lint-values.yaml | 4 +-- .../ci/network-adapter-values.yaml | 6 ++--- .../ci/otel-ingress-values.yaml | 4 +-- .../network-adapter-config.yaml | 8 +++--- charts/adapter-service/examples/consumer.yaml | 4 +-- charts/adapter-service/examples/network.yaml | 10 +++---- charts/adapter-service/examples/provider.yaml | 4 +-- charts/adapter-service/templates/NOTES.txt | 6 ++--- charts/adapter-service/templates/_helpers.tpl | 12 +++++---- .../templates/_network-adapter.tpl | 24 ++++++++--------- .../adapter-service/templates/configmap.yaml | 22 +++++++-------- .../adapter-service/templates/deployment.yaml | 12 ++++----- charts/adapter-service/templates/ingress.yaml | 2 +- charts/adapter-service/values.yaml | 27 ++++++++++--------- 15 files changed, 75 insertions(+), 72 deletions(-) diff --git a/charts/adapter-service/Chart.yaml b/charts/adapter-service/Chart.yaml index 3210f813..b7e45e17 100644 --- a/charts/adapter-service/Chart.yaml +++ b/charts/adapter-service/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 name: adapter-service -description: OAN Beckn adapter; role network runs all three tiers in single mode (default), or the network tier in multi. +description: OAN Beckn adapter; role network-adapter runs all three tiers in single-adapter mode (default), or the network tier in multi-adapter. type: application version: 0.6.0 # The adapter binary, not this chart. `/health` reports the build it is diff --git a/charts/adapter-service/ci/lint-values.yaml b/charts/adapter-service/ci/lint-values.yaml index d01cdfad..06217d9e 100644 --- a/charts/adapter-service/ci/lint-values.yaml +++ b/charts/adapter-service/ci/lint-values.yaml @@ -3,7 +3,7 @@ # # role is first among them: it has no default, because a default would hand one # adapter another one's handler role and step list. -role: network +role: network-adapter fullnameOverride: network-adapter image: @@ -18,6 +18,6 @@ keys: registry: url: http://registry:8081/api/v1 -# Routing is no longer a value: it comes from config/routing-.yaml in +# Routing is no longer a value: it comes from config/multi-adapter/routing-.yaml in # the chart, like the adapter config. Set `routing.config` to a whole routing # file only if this deployment must differ. diff --git a/charts/adapter-service/ci/network-adapter-values.yaml b/charts/adapter-service/ci/network-adapter-values.yaml index 91f4744c..8465cdcd 100644 --- a/charts/adapter-service/ci/network-adapter-values.yaml +++ b/charts/adapter-service/ci/network-adapter-values.yaml @@ -1,6 +1,6 @@ -# CI only: renders single mode (generated routing, tier identities) +# CI only: renders single-adapter mode (generated routing, tier identities) # with ingress on and annotations null to exercise the path guard. -role: network +role: network-adapter fullnameOverride: network-adapter image: @@ -9,7 +9,7 @@ image: tag: "v1.9.0" # registry.url, otel.*, appName, logLevel, http.timeout.*, routing.* and handler.* -# are unused in single mode: set them in config/network-adapter-config.yaml. +# are unused in single-adapter mode: set them in config/single-adapter/network-adapter-config.yaml. service: port: 9201 diff --git a/charts/adapter-service/ci/otel-ingress-values.yaml b/charts/adapter-service/ci/otel-ingress-values.yaml index a0e9c203..6762dcff 100644 --- a/charts/adapter-service/ci/otel-ingress-values.yaml +++ b/charts/adapter-service/ci/otel-ingress-values.yaml @@ -4,7 +4,7 @@ # Uses the consumer role on purpose: it is the one with a different handler role and # a shorter step list, so this case renders the role branches that # ci/lint-values.yaml (network) does not. -role: consumer +role: consumer-adapter fullnameOverride: consumer-adapter image: @@ -19,7 +19,7 @@ keys: registry: url: http://registry:8081/api/v1 -# Routing is no longer a value: it comes from config/routing-.yaml in +# Routing is no longer a value: it comes from config/multi-adapter/routing-.yaml in # the chart, like the adapter config. Set `routing.config` to a whole routing # file only if this deployment must differ. diff --git a/charts/adapter-service/config/single-adapter/network-adapter-config.yaml b/charts/adapter-service/config/single-adapter/network-adapter-config.yaml index 8eab2778..8110d15f 100644 --- a/charts/adapter-service/config/single-adapter/network-adapter-config.yaml +++ b/charts/adapter-service/config/single-adapter/network-adapter-config.yaml @@ -36,7 +36,7 @@ plugins: modules: # consumer: unsigned app calls at / (the catch-all mount, on purpose: see ADR-0001); # discover to /_internal/network, transactions to /_internal/provider. - - name: consumer + - name: consumer-adapter path: / handler: type: std @@ -102,7 +102,7 @@ modules: # network: verifies the caller, forwards discover and publish to discovery. # Internal: never expose it on an Ingress. - - name: network + - name: network-adapter path: /_internal/network/ handler: type: std @@ -169,7 +169,7 @@ modules: # provider: verifies the caller and answers from the upstream APIs (no router); # upstream credentials come from envFromSecrets: provider-upstream. - - name: provider + - name: oanProvider path: /_internal/provider/ handler: type: std @@ -273,7 +273,7 @@ modules: # provider-publish: the catalogue system publishes at /publish, signed as the # provider. No validateSign: never expose it on an Ingress. - - name: provider-publish + - name: oanProviderPublish path: /publish handler: type: std diff --git a/charts/adapter-service/examples/consumer.yaml b/charts/adapter-service/examples/consumer.yaml index 0b74cb68..a58caaef 100644 --- a/charts/adapter-service/examples/consumer.yaml +++ b/charts/adapter-service/examples/consumer.yaml @@ -13,7 +13,7 @@ # routing.rules points at the network adapter by RELEASE name. Rename that # release and this must change with it. # =========================================================================== -role: consumer +role: consumer-adapter # The Service name, and therefore the in-cluster DNS name other adapters use. # Set explicitly because fullname is otherwise "-" -- release @@ -46,7 +46,7 @@ handler: - addRoute - sign -# Routing is no longer a value: it comes from config/routing-.yaml in +# Routing is no longer a value: it comes from config/multi-adapter/routing-.yaml in # the chart, like the adapter config. Set `routing.config` to a whole routing # file only if this deployment must differ. diff --git a/charts/adapter-service/examples/network.yaml b/charts/adapter-service/examples/network.yaml index df93f532..913f1b9d 100644 --- a/charts/adapter-service/examples/network.yaml +++ b/charts/adapter-service/examples/network.yaml @@ -1,17 +1,17 @@ # =========================================================================== -# network role. networkAdapterDeploymentMode decides: single (default) runs all -# three tiers; multi runs the network tier alone. +# network-adapter role. deploymentMode decides: single-adapter (default) runs all +# three tiers; multi-adapter runs the network tier alone. # # Verifies that the caller's signature checks out against the key the registry # publishes for them, hands discover and publish to the discovery service, and # signs on the way out. It answers neither action itself. # # helm install network-adapter . -n network-adapter -f examples/network.yaml \ -# --set networkAdapterDeploymentMode=single # or multi +# --set deploymentMode=single-adapter # or multi-adapter # # Keep the release name `network-adapter`: consumer routes to this Service by name. # =========================================================================== -role: network +role: network-adapter # The Service name, and therefore the in-cluster DNS name other adapters use. # Set explicitly because fullname is otherwise "-" -- release @@ -38,7 +38,7 @@ service: # Both actions share one rule because they share a target: publish arrives # from the provider adapter, discover from the consumer adapter, and neither # is answered here. -# Routing is no longer a value: it comes from config/routing-.yaml in +# Routing is no longer a value: it comes from config/multi-adapter/routing-.yaml in # the chart, like the adapter config. Set `routing.config` to a whole routing # file only if this deployment must differ. diff --git a/charts/adapter-service/examples/provider.yaml b/charts/adapter-service/examples/provider.yaml index 96f89b71..da98997e 100644 --- a/charts/adapter-service/examples/provider.yaml +++ b/charts/adapter-service/examples/provider.yaml @@ -11,7 +11,7 @@ # call time, so it never lands in a config file or in the registry -- put it in # a Secret and reference it, do not inline it. # =========================================================================== -role: provider +role: provider-adapter # The Service name, and therefore the in-cluster DNS name other adapters use. # Set explicitly because fullname is otherwise "-" -- release @@ -37,7 +37,7 @@ service: # One rule per upstream. Each target.url is a bare host and port -- the router # appends the action, so no path and no trailing slash. -# Routing is no longer a value: it comes from config/routing-.yaml in +# Routing is no longer a value: it comes from config/multi-adapter/routing-.yaml in # the chart, like the adapter config. Set `routing.config` to a whole routing # file only if this deployment must differ. diff --git a/charts/adapter-service/templates/NOTES.txt b/charts/adapter-service/templates/NOTES.txt index 7ab13956..9d6e968f 100644 --- a/charts/adapter-service/templates/NOTES.txt +++ b/charts/adapter-service/templates/NOTES.txt @@ -1,4 +1,4 @@ -{{- $single := eq (include "adapter-service.deploymentMode" .) "single" -}} +{{- $single := eq (include "adapter-service.deploymentMode" .) "single-adapter" -}} {{ .Chart.Name }} {{ .Chart.Version }} — {{ include "adapter-service.fullname" . }} {{- if $single }} @@ -13,7 +13,7 @@ service {{ include "adapter-service.fullname" . }}:{{ .Values.service.port }} {{- if not $single }} registry {{ .Values.registry.url }} - routes from config/routing-{{ include "adapter-service.role" . }}.yaml + routes from config/multi-adapter/routing-{{ include "adapter-service.role" . }}.yaml telemetry {{ if .Values.otel.enabled }}{{ .Values.otel.endpoint }}{{ else }}off{{ end }} {{- end }} @@ -33,7 +33,7 @@ TWO THINGS THIS CHART CANNOT CHECK FOR YOU {{ if $single -}} 1. The Participant rows. Each tier signs from its own Secret - (networkTierKeys); each registry row must carry that tier's public key. + (adapterKeys); each registry row must carry that tier's public key. {{- else -}} 1. The Participant row. This adapter signs as {{ .Values.keys.existingSecret.name }}'s subscriberId, and every peer verifies that signature against the public key diff --git a/charts/adapter-service/templates/_helpers.tpl b/charts/adapter-service/templates/_helpers.tpl index ccbf19ad..ddbdd708 100644 --- a/charts/adapter-service/templates/_helpers.tpl +++ b/charts/adapter-service/templates/_helpers.tpl @@ -14,7 +14,8 @@ */}} {{/* -The adapter's role: provider | network | consumer. +The adapter's role: provider-adapter | network-adapter | consumer-adapter (old names +provider | network | consumer still accepted). Returns the tier: provider | network | consumer. Required, with no default. A default would silently give one role's step list and handler role to a different adapter -- which renders, starts, reports Ready @@ -23,12 +24,13 @@ and then mis-handles every request, at the far end, in a peer's logs. {{- define "adapter-service.role" -}} {{- $role := .Values.role | default "" -}} {{- if not $role -}} -{{- fail (printf "%s: role is required -- one of provider, network, consumer. It decides the handler role, the step list and the routing target, so there is no safe default. See examples/ for a values file per role." .Chart.Name) -}} +{{- fail (printf "%s: role is required -- one of provider-adapter, network-adapter, consumer-adapter. It decides the handler role, the step list and the routing target, so there is no safe default. See examples/ for a values file per role." .Chart.Name) -}} {{- end -}} -{{- if not (has $role (list "provider" "network" "consumer")) -}} -{{- fail (printf "%s: role must be one of provider, network, consumer (got %q)." .Chart.Name $role) -}} +{{- $tier := trimSuffix "-adapter" $role -}} +{{- if not (has $tier (list "provider" "network" "consumer")) -}} +{{- fail (printf "%s: role must be one of provider-adapter, network-adapter, consumer-adapter (got %q)." .Chart.Name $role) -}} {{- end -}} -{{- $role -}} +{{- $tier -}} {{- end }} {{/* diff --git a/charts/adapter-service/templates/_network-adapter.tpl b/charts/adapter-service/templates/_network-adapter.tpl index 0fadbbf5..5bf1dac7 100644 --- a/charts/adapter-service/templates/_network-adapter.tpl +++ b/charts/adapter-service/templates/_network-adapter.tpl @@ -1,13 +1,13 @@ -{{/* Single mode (role network): three tiers in one adapter; config from -config/network-adapter-config.yaml, routing generated below (ADR-0001). */}} +{{/* Single mode (role network-adapter): three tiers in one adapter; config from +config/single-adapter/network-adapter-config.yaml, routing generated below (ADR-0001). */}} -{{/* networkAdapterDeploymentMode (single | multi); provider and consumer are always multi. */}} +{{/* deploymentMode (single-adapter | multi-adapter); provider and consumer are always multi-adapter. */}} {{- define "adapter-service.deploymentMode" -}} -{{- $mode := .Values.networkAdapterDeploymentMode | default "single" -}} -{{- if not (has $mode (list "single" "multi")) -}} -{{- fail (printf "%s: networkAdapterDeploymentMode must be single or multi (got %q)." .Chart.Name $mode) -}} +{{- $mode := .Values.deploymentMode | default "single-adapter" -}} +{{- if not (has $mode (list "single-adapter" "multi-adapter")) -}} +{{- fail (printf "%s: deploymentMode must be single-adapter or multi-adapter (got %q)." .Chart.Name $mode) -}} {{- end -}} -{{- ternary $mode "multi" (eq (include "adapter-service.role" .) "network") -}} +{{- ternary $mode "multi-adapter" (eq (include "adapter-service.role" .) "network") -}} {{- end }} {{/* Fails unless every module's private keys are tier placeholders. @@ -34,16 +34,16 @@ become http://localhost:/_internal/ (ADR-0001); routing-networ {{- $out := dict -}} {{- range $pair := list (list "routing-consumer.yaml" "routing-network-adapter-consumer.yaml") (list "routing-network.yaml" "routing-network.yaml") (list "routing-provider.yaml" "routing-network-adapter-provider.yaml") -}} {{- $src := index $pair 0 -}} -{{- $doc := $ctx.Files.Get (printf "config/%s" $src) | fromYaml -}} +{{- $doc := $ctx.Files.Get (printf "config/multi-adapter/%s" $src) | fromYaml -}} {{- if not $doc.routingRules -}} -{{- fail (printf "%s: config/%s parsed to no routingRules; the single-mode routing is generated from it." $ctx.Chart.Name $src) -}} +{{- fail (printf "%s: config/multi-adapter/%s parsed to no routingRules; the single-mode routing is generated from it." $ctx.Chart.Name $src) -}} {{- end -}} {{- range $i, $rule := $doc.routingRules -}} {{- if $rule.target -}} {{- $url := $rule.target.url | default "" -}} {{- $url = regexReplaceAll $tierHost $url $ownListener -}} {{- if hasSuffix "/" $url -}} -{{- fail (printf "%s: single-mode routing from config/%s routingRules[%d] ends in a slash (%q). The router appends the action, so that produces //discover." $ctx.Chart.Name $src $i $url) -}} +{{- fail (printf "%s: single-mode routing from config/multi-adapter/%s routingRules[%d] ends in a slash (%q). The router appends the action, so that produces //discover." $ctx.Chart.Name $src $i $url) -}} {{- end -}} {{- $_ := set $rule.target "url" $url -}} {{- end -}} @@ -56,9 +56,9 @@ become http://localhost:/_internal/ (ADR-0001); routing-networ {{/* One tier's key Secret; fails naming the tier when unset. Usage: include "adapter-service.networkTierSecret" (dict "ctx" $ "tier" "network") */}} {{- define "adapter-service.networkTierSecret" -}} -{{- $name := index .ctx.Values.networkTierKeys .tier | default "" -}} +{{- $name := index .ctx.Values.adapterKeys (printf "%s-adapter" .tier) | default "" -}} {{- if not $name -}} -{{- fail (printf "%s: networkTierKeys.%s is required -- the %s tier signs as its own identity, from its own Secret (the six keys under keys.existingSecret)." .ctx.Chart.Name .tier .tier) -}} +{{- fail (printf "%s: adapterKeys.%s-adapter is required -- the %s tier signs as its own identity, from its own Secret (the six keys under keys.existingSecret)." .ctx.Chart.Name .tier .tier) -}} {{- end -}} {{- $name -}} {{- end }} diff --git a/charts/adapter-service/templates/configmap.yaml b/charts/adapter-service/templates/configmap.yaml index 0d5f4c77..2738891c 100644 --- a/charts/adapter-service/templates/configmap.yaml +++ b/charts/adapter-service/templates/configmap.yaml @@ -10,7 +10,7 @@ Everything that differs between the provider, network and consumer adapters is a value: they are one image with one config format, and the role decides the identity, the handler role, the step list and where requests are routed. -Single mode reads config/network-adapter-config.yaml and renders the generated +Single mode reads config/single-adapter/network-adapter-config.yaml and renders the generated routing files (_network-adapter.tpl). */}} {{/* @@ -27,25 +27,25 @@ at pod start, which is why no rendered config ever holds a key. A raw config with a literal private key in it would put that key in git, in a ConfigMap, and in `helm get values` -- so the render refuses it. */}} -{{- $single := eq (include "adapter-service.deploymentMode" .) "single" }} +{{- $single := eq (include "adapter-service.deploymentMode" .) "single-adapter" }} {{- $nlCfg := "" }} {{- if $single }} -{{- /* Single mode: config/network-adapter-config.yaml unless `config` replaces it; +{{- /* Single mode: config/single-adapter/network-adapter-config.yaml unless `config` replaces it; private keys must be tier placeholders either way. */}} -{{- $nlCfg = .Values.config | default (.Files.Get "config/network-adapter-config.yaml") }} +{{- $nlCfg = .Values.config | default (.Files.Get "config/single-adapter/network-adapter-config.yaml") }} {{- if not $nlCfg }} -{{- fail (printf "%s: role network in single mode reads config/network-adapter-config.yaml, which is missing from the chart." .Chart.Name) }} +{{- fail (printf "%s: role network-adapter in single mode reads config/single-adapter/network-adapter-config.yaml, which is missing from the chart." .Chart.Name) }} {{- end }} {{- $nlParsed := $nlCfg | fromYaml }} {{- if or (not $nlParsed) (hasKey $nlParsed "Error") }} -{{- fail (printf "%s: the single-mode adapter config (`config` override or config/network-adapter-config.yaml) does not parse as YAML, so its private keys cannot be checked." .Chart.Name) }} +{{- fail (printf "%s: the single-mode adapter config (`config` override or config/single-adapter/network-adapter-config.yaml) does not parse as YAML, so its private keys cannot be checked." .Chart.Name) }} {{- end }} {{- include "adapter-service.networkCheckTierKeys" (dict "ctx" . "cfg" $nlParsed) }} {{- if ne (toString (dig "http" "port" "" $nlParsed)) (toString .Values.service.targetPort) }} {{- fail (printf "%s: the single-mode config listens on http.port %v but service.targetPort is %v. The generated routing sends tier hops to localhost:, so the two must match." .Chart.Name (dig "http" "port" "unset" $nlParsed) .Values.service.targetPort) }} {{- end }} {{- else }} -{{- $cfgFile := printf "config/%s-config.yaml" (include "adapter-service.role" .) }} +{{- $cfgFile := printf "config/multi-adapter/%s-config.yaml" (include "adapter-service.role" .) }} {{- $cfg := .Values.config | default (.Files.Get $cfgFile) }} {{- if $cfg }} {{- range $ph, $field := dict "__ADAPTER_SIGNING_PRIVATE__" "signingPrivateKey" "__ADAPTER_ENCR_PRIVATE__" "encrPrivateKey" }} @@ -70,11 +70,11 @@ data: adapter.yaml: | {{- $nlCfg | nindent 4 }} {{- if .Values.routing.config }} - {{- fail (printf "%s: routing.config is not used in the single deployment mode. Its routing files are generated from config/routing-{consumer,network,provider}.yaml. To change a route edit those files; to replace the whole adapter config set `config`." .Chart.Name) }} + {{- fail (printf "%s: routing.config is not used in the single deployment mode. Its routing files are generated from config/multi-adapter/routing-{consumer,network,provider}.yaml. To change a route edit those files; to replace the whole adapter config set `config`." .Chart.Name) }} {{- end }} {{- range $file, $doc := include "adapter-service.networkSingleRouting" . | fromYaml }} {{ $file }}: | - # GENERATED by adapter-service from config/routing-*.yaml. Do not edit; edit those files. + # GENERATED by adapter-service from config/multi-adapter/routing-*.yaml. Do not edit; edit those files. {{- toYaml $doc | nindent 4 }} {{- end }} {{- else }} @@ -89,7 +89,7 @@ data: `config` in values wins when set, for an environment that needs to differ without a new file. */}} - {{- $file := printf "config/%s-config.yaml" (include "adapter-service.role" .) }} + {{- $file := printf "config/multi-adapter/%s-config.yaml" (include "adapter-service.role" .) }} {{- $raw := .Values.config | default ($.Files.Get $file) }} {{- if not $raw }} {{- fail (printf "%s: no config. Expected %s in the chart, or `config` set in values. The role decides the filename, so a new role needs a new file." .Chart.Name $file) }} @@ -106,7 +106,7 @@ data: `routing.config` in values wins when set, for an environment that genuinely has to differ. */}} - {{- $routeFile := printf "config/routing-%s.yaml" (include "adapter-service.role" .) }} + {{- $routeFile := printf "config/multi-adapter/routing-%s.yaml" (include "adapter-service.role" .) }} {{- $routes := .Values.routing.config | default ($.Files.Get $routeFile) }} {{- if not $routes }} {{- fail (printf "%s: no routing. Expected %s in the chart, or `routing.config` set in values. An adapter with no route accepts requests and has nowhere to send them." .Chart.Name $routeFile) }} diff --git a/charts/adapter-service/templates/deployment.yaml b/charts/adapter-service/templates/deployment.yaml index e361b096..aeab01ea 100644 --- a/charts/adapter-service/templates/deployment.yaml +++ b/charts/adapter-service/templates/deployment.yaml @@ -1,5 +1,5 @@ apiVersion: {{ include "common.deployment.apiVersion" . }} -{{- $single := eq (include "adapter-service.deploymentMode" .) "single" }} +{{- $single := eq (include "adapter-service.deploymentMode" .) "single-adapter" }} kind: Deployment metadata: name: {{ include "adapter-service.fullname" . }} @@ -72,7 +72,7 @@ spec: - | set -eu {{- if $single }} - # single mode: adapter.yaml plus one routing file per routed module. + # single-adapter mode: adapter.yaml plus one routing file per routed module. cp /config-template/*.yaml /rendered/ {{- else }} cp /config-template/adapter.yaml /rendered/adapter.yaml @@ -121,7 +121,7 @@ spec: subst __{{ $T }}_ENCR_PRIVATE__ /keys/{{ $tier }}/{{ $k.encrPrivateKey }} subst __{{ $T }}_ENCR_PUBLIC__ /keys/{{ $tier }}/{{ $k.encrPublicKey }} {{- end }} - {{- range $ph, $path := .Values.networkTierKeys.extraSubstitutions }} + {{- range $ph, $path := .Values.adapterKeys.extraSubstitutions }} subst {{ $ph }} /keys/{{ $path }} {{- end }} {{- else }} @@ -149,7 +149,7 @@ spec: grep -oE '__[A-Z][A-Z0-9_]*__' /rendered/adapter.yaml | sort -u >&2 exit 1 fi - echo "render-config: config rendered for the {{ include "adapter-service.role" . }} role{{ if $single }} (single mode: all three tiers){{ end }}" + echo "render-config: config rendered for the {{ include "adapter-service.role" . }} role{{ if $single }} (single-adapter mode: all three tiers){{ end }}" volumeMounts: - name: config-template mountPath: /config-template @@ -195,8 +195,8 @@ spec: {{- toYaml . | nindent 12 }} {{- end }} {{- $envFrom := .Values.envFromSecrets | default list }} - {{- if and $single .Values.networkTierKeys.upstreamSecret }} - {{- $envFrom = append $envFrom .Values.networkTierKeys.upstreamSecret }} + {{- if and $single .Values.adapterKeys.upstreamSecret }} + {{- $envFrom = append $envFrom .Values.adapterKeys.upstreamSecret }} {{- end }} {{- with $envFrom }} envFrom: diff --git a/charts/adapter-service/templates/ingress.yaml b/charts/adapter-service/templates/ingress.yaml index 02daa17b..328da280 100644 --- a/charts/adapter-service/templates/ingress.yaml +++ b/charts/adapter-service/templates/ingress.yaml @@ -1,6 +1,6 @@ {{- if .Values.ingress.enabled }} {{- $ann := merge (dict) (.Values.ingress.annotations | default dict) (.Values.commonAnnotations | default dict) }} -{{- $single := eq (include "adapter-service.deploymentMode" .) "single" }} +{{- $single := eq (include "adapter-service.deploymentMode" .) "single-adapter" }} {{- if $single }} {{- /* Single mode: only literal one-segment / paths (Prefix/Exact, no regex or rewrite); never /, /publish or /_internal* (ADR-0001). */}} diff --git a/charts/adapter-service/values.yaml b/charts/adapter-service/values.yaml index 5e5f4447..116a8909 100644 --- a/charts/adapter-service/values.yaml +++ b/charts/adapter-service/values.yaml @@ -11,8 +11,8 @@ # helm install network-adapter . -f examples/network.yaml # helm install consumer-adapter . -f examples/consumer.yaml # -# Role network defaults to single mode, all three tiers in one release -# (examples/network.yaml); see networkAdapterDeploymentMode. +# Role network-adapter defaults to single-adapter mode, all three tiers in one release +# (examples/network.yaml); see deploymentMode. # # The release name is what the Service is called, which is the DNS name the # other adapters address. Rename a release and you must update whatever routes @@ -28,7 +28,8 @@ # =========================================================================== # --------------------------------------------------------------------------- -# Role: provider | network | consumer +# Role: provider-adapter | network-adapter | consumer-adapter (provider | network | consumer +# still accepted) # # Required, with no default. A default would hand one role's handler role and # step list to a different adapter, which renders, starts, reports Ready and @@ -36,9 +37,9 @@ # --------------------------------------------------------------------------- role: "" -# single (default): role network runs all three tiers; multi: the network tier alone. -# A multi network release must set multi. provider and consumer ignore it. -networkAdapterDeploymentMode: single +# single-adapter (default): role network-adapter runs all three tiers; multi-adapter: the +# network tier alone. provider-adapter and consumer-adapter ignore it. +deploymentMode: single-adapter # Overrides for what the role implies. Leave empty to take the role's default. appName: "" @@ -210,8 +211,8 @@ handler: # --------------------------------------------------------------------------- # Routing # -# WHERE THE ROUTES ACTUALLY LIVE: config/routing-.yaml in this chart, -# mounted verbatim, exactly like config/-config.yaml beside it. +# WHERE THE ROUTES ACTUALLY LIVE: config/multi-adapter/routing-.yaml in this chart, +# mounted verbatim, exactly like config/multi-adapter/-config.yaml beside it. # # Routing is topology rather than environment. The in-cluster targets follow the # namespace-per-component convention and are the same in every environment, so @@ -412,11 +413,11 @@ envFromSecrets: [] # Single mode only: one key Secret per tier (six keys each, as under keys.existingSecret), # named as openbao-secrets creates them. routing.config and handler.* are unused. -networkTierKeys: - consumer: consumer-adapter-keys - network: network-adapter-keys - provider: provider-adapter-keys - # The provider tier's upstream credentials, loaded as env vars in single mode. +adapterKeys: + consumer-adapter: consumer-adapter-keys + network-adapter: network-adapter-keys + provider-adapter: provider-adapter-keys + # The provider tier's upstream credentials, loaded as env vars in single-adapter mode. upstreamSecret: provider-upstream # Extra placeholders, as "/", e.g. # __VISTAAR_TOKEN_URL__: provider/vistaarTokenUrl From a94c3a672f45f67ba30701d73dd2f6c0535ad8a6 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Fri, 23 Oct 2026 17:28:19 +0530 Subject: [PATCH 09/10] refactor(adapter-service): example files use the -adapter suffix (#55, #66) Addresses review 5627785485: examples/consumer.yaml, network.yaml and provider.yaml become consumer-adapter.yaml, network-adapter.yaml and provider-adapter.yaml, matching the role names. Example headers and values.yaml comments follow. Refs: #55, #66, OpenAgriNet/network-adapter#55, OpenAgriNet/engineering-tracker#171 --- .../examples/{consumer.yaml => consumer-adapter.yaml} | 2 +- .../examples/{network.yaml => network-adapter.yaml} | 2 +- .../examples/{provider.yaml => provider-adapter.yaml} | 2 +- charts/adapter-service/values.yaml | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) rename charts/adapter-service/examples/{consumer.yaml => consumer-adapter.yaml} (96%) rename charts/adapter-service/examples/{network.yaml => network-adapter.yaml} (98%) rename charts/adapter-service/examples/{provider.yaml => provider-adapter.yaml} (96%) diff --git a/charts/adapter-service/examples/consumer.yaml b/charts/adapter-service/examples/consumer-adapter.yaml similarity index 96% rename from charts/adapter-service/examples/consumer.yaml rename to charts/adapter-service/examples/consumer-adapter.yaml index a58caaef..f01e6cee 100644 --- a/charts/adapter-service/examples/consumer.yaml +++ b/charts/adapter-service/examples/consumer-adapter.yaml @@ -8,7 +8,7 @@ # # It is also why the edge should rate-limit this host and nothing else. # -# helm install consumer-adapter . -f examples/consumer.yaml +# helm install consumer-adapter . -f examples/consumer-adapter.yaml # # routing.rules points at the network adapter by RELEASE name. Rename that # release and this must change with it. diff --git a/charts/adapter-service/examples/network.yaml b/charts/adapter-service/examples/network-adapter.yaml similarity index 98% rename from charts/adapter-service/examples/network.yaml rename to charts/adapter-service/examples/network-adapter.yaml index 913f1b9d..dd40c213 100644 --- a/charts/adapter-service/examples/network.yaml +++ b/charts/adapter-service/examples/network-adapter.yaml @@ -6,7 +6,7 @@ # publishes for them, hands discover and publish to the discovery service, and # signs on the way out. It answers neither action itself. # -# helm install network-adapter . -n network-adapter -f examples/network.yaml \ +# helm install network-adapter . -n network-adapter -f examples/network-adapter.yaml \ # --set deploymentMode=single-adapter # or multi-adapter # # Keep the release name `network-adapter`: consumer routes to this Service by name. diff --git a/charts/adapter-service/examples/provider.yaml b/charts/adapter-service/examples/provider-adapter.yaml similarity index 96% rename from charts/adapter-service/examples/provider.yaml rename to charts/adapter-service/examples/provider-adapter.yaml index da98997e..9ef4053f 100644 --- a/charts/adapter-service/examples/provider.yaml +++ b/charts/adapter-service/examples/provider-adapter.yaml @@ -5,7 +5,7 @@ # the upstream APIs that actually answer it. Unlike the other two roles it has # more than one target, which is why routing.rules is a list. # -# helm install provider-adapter . -f examples/provider.yaml +# helm install provider-adapter . -f examples/provider-adapter.yaml # # MANDI_TOKEN is the mandi upstream's credential. It is named here and read at # call time, so it never lands in a config file or in the registry -- put it in diff --git a/charts/adapter-service/values.yaml b/charts/adapter-service/values.yaml index 116a8909..ef5640c7 100644 --- a/charts/adapter-service/values.yaml +++ b/charts/adapter-service/values.yaml @@ -7,12 +7,12 @@ # # INSTALL ONCE PER ROLE, and keep the RELEASE name per-role: # -# helm install provider-adapter . -f examples/provider.yaml -# helm install network-adapter . -f examples/network.yaml -# helm install consumer-adapter . -f examples/consumer.yaml +# helm install provider-adapter . -f examples/provider-adapter.yaml +# helm install network-adapter . -f examples/network-adapter.yaml +# helm install consumer-adapter . -f examples/consumer-adapter.yaml # # Role network-adapter defaults to single-adapter mode, all three tiers in one release -# (examples/network.yaml); see deploymentMode. +# (examples/network-adapter.yaml); see deploymentMode. # # The release name is what the Service is called, which is the DNS name the # other adapters address. Rename a release and you must update whatever routes From d1c33afde5628d63d5a307d0be1c939f841eb610 Mon Sep 17 00:00:00 2001 From: Kelvin Date: Fri, 23 Oct 2026 17:49:42 +0530 Subject: [PATCH 10/10] fix(adapter-service): ingress guard blocks snippet annotations; comment fixes (#55, #66) From the code review: - Single-adapter ingress refuses any snippet, rewrite or regex annotation (e.g. configuration-snippet, server-snippet, another controller's rewrite-target), not only nginx use-regex and rewrite-target; any of them can route a request onto /_internal or /publish. - network-adapter-config.yaml: the Vistaar token URL comment names adapterKeys.extraSubstitutions (was the old networkAdapterLayer key). - values.yaml: otel.serviceName comment says only the uncalled otelServiceName helper reads it. Refs: #55, #66, OpenAgriNet/network-adapter#55, OpenAgriNet/engineering-tracker#171 --- .../config/single-adapter/network-adapter-config.yaml | 2 +- charts/adapter-service/templates/ingress.yaml | 11 +++++------ charts/adapter-service/values.yaml | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/charts/adapter-service/config/single-adapter/network-adapter-config.yaml b/charts/adapter-service/config/single-adapter/network-adapter-config.yaml index 8110d15f..407a0906 100644 --- a/charts/adapter-service/config/single-adapter/network-adapter-config.yaml +++ b/charts/adapter-service/config/single-adapter/network-adapter-config.yaml @@ -251,7 +251,7 @@ modules: bharat-vistaar: authScheme: oauth2 # Substituted from the provider Secret (vistaarTokenUrl) via - # networkAdapterLayer.extraSubstitutions. + # adapterKeys.extraSubstitutions. tokenUrl: "__VISTAAR_TOKEN_URL__" clientIdEnv: VISTAAR_CLIENT_ID clientSecretEnv: VISTAAR_CLIENT_SECRET diff --git a/charts/adapter-service/templates/ingress.yaml b/charts/adapter-service/templates/ingress.yaml index 328da280..18339e1a 100644 --- a/charts/adapter-service/templates/ingress.yaml +++ b/charts/adapter-service/templates/ingress.yaml @@ -2,13 +2,12 @@ {{- $ann := merge (dict) (.Values.ingress.annotations | default dict) (.Values.commonAnnotations | default dict) }} {{- $single := eq (include "adapter-service.deploymentMode" .) "single-adapter" }} {{- if $single }} -{{- /* Single mode: only literal one-segment / paths (Prefix/Exact, no regex or - rewrite); never /, /publish or /_internal* (ADR-0001). */}} -{{- if eq (index $ann "nginx.ingress.kubernetes.io/use-regex" | default "" | toString | trim | lower) "true" }} -{{- fail (printf "%s: ingress annotation nginx.ingress.kubernetes.io/use-regex is not allowed in the single deployment mode. A regex path can match /_internal or /publish." .Chart.Name) }} +{{- /* Single mode: only literal one-segment / paths (Prefix/Exact); no snippet, + rewrite or regex annotation; never /, /publish or /_internal* (ADR-0001). */}} +{{- range $k, $_ := $ann }} +{{- if regexMatch "(?i)(snippet|rewrite|regex)" $k }} +{{- fail (printf "%s: ingress annotation %s is not allowed in the single deployment mode. Snippet, rewrite and regex annotations can reach /_internal or /publish." $.Chart.Name $k) }} {{- end }} -{{- if hasKey $ann "nginx.ingress.kubernetes.io/rewrite-target" }} -{{- fail (printf "%s: ingress annotation nginx.ingress.kubernetes.io/rewrite-target is not allowed in the single deployment mode. It turns paths into regexes and can rewrite a request onto /_internal or /publish." .Chart.Name) }} {{- end }} {{- range .Values.ingress.hosts }} {{- range .paths }} diff --git a/charts/adapter-service/values.yaml b/charts/adapter-service/values.yaml index ef5640c7..a00d2404 100644 --- a/charts/adapter-service/values.yaml +++ b/charts/adapter-service/values.yaml @@ -269,8 +269,8 @@ otel: # OTLP/gRPC, e.g. "otel-collector.observability.svc.cluster.local:4317" endpoint: "" environment: dev - # Not read by any template: the role's config file sets it - # (plugins.otelsetup.config.serviceName). + # Unused: the role's config file sets the service name (plugins.otelsetup.config.serviceName); + # only the uncalled adapter-service.otelServiceName helper reads this. serviceName: "" logLevel: info