Skip to content

feat: chart repo scaffold, common library and the first service charts - #8

Merged
manjudr merged 19 commits into
feat/4-docker-composefrom
feat/53-chart-scaffold-common-library
Sep 8, 2026
Merged

feat: chart repo scaffold, common library and the first service charts#8
manjudr merged 19 commits into
feat/4-docker-composefrom
feat/53-chart-scaffold-common-library

Conversation

@KrutikaPhirangi

Copy link
Copy Markdown

Status

These are draft charts and are not yet deployed in dev.

What

Establishes the chart repository: conventions, the oan-common library chart
every service chart builds on, a oan-template starter, and the first service
charts.

Why

Each OAN service otherwise repeats the same labels, probes, security context,
service account and config-rendering boilerplate, and drifts. oan-common
holds that once as a library chart so a new service chart is values plus
whatever is genuinely specific to it. oan-template is the copy-to-start
scaffold so the shape stays consistent.

Changes

  • oan-common — library chart: shared helpers, labels, probes, security
    context, service account, config-rendering pattern.
  • oan-template — starter chart for a new service.
  • postgresql-cnpg and postgresql-migration — CNPG cluster and Flyway
    migration job.
  • keycloak — with sunbird-rc realm import.
  • registry and discovery — the two core network services.
  • network-adapter — Beckn network adapter. Identity is a keypair in a Secret,
    substituted by an init container into an emptyDir, so it is never written
    to a ConfigMap and never lands in a process environment.
  • scripts/lint-charts.sh + helm-lint workflow — lints and renders every
    chart on PR. Each ci/*-values.yaml is a separate render case, so charts
    cover both their default and toggled-on branches.
  • README documenting the seven charts and their install order.

Notes

  • Charts consume oan-common via file://../oan-common, so no dependency
    artifact is committed. scripts/lint-charts.sh rebuilds it before linting;
    a local edit to oan-common needs that script (or helm dependency update)
    before it is picked up.
  • Earlier commits on this branch carry [#53], which refers to
    OpenAgriNet/engineering-tracker#53. Read inside this repo that number
    resolves to the Discover Service ticket instead, so the later commits use
    in-repo issue numbers.

Refs OpenAgriNet/engineering-tracker#53 — [OAN-HELM-1] Chart repo scaffold, conventions and common library chart
Refs #1 — [OAN-HELM-2] Helm chart for Registry Service
Refs #2 — [OAN-HELM-3] Helm chart for Adapter Service
Refs #3 — [OAN-HELM-4] Helm chart for Discover Service

Establishes the repo-level scaffolding the charts depend on: naming and
structure conventions, a lint script that rebuilds file:// dependencies
before linting, and a CI workflow that runs it. .gitignore excludes Helm
dependency artifacts, which are regenerated rather than committed.
oan-common holds the shared helpers every OAN chart renders through -
names, labels, image references, resources, and security contexts - so
the service charts stay declarative. Resources are deliberately required
rather than defaulted: an unset value fails the render instead of
shipping an unbounded pod.

oan-template is the copy-from starting point for a new service chart.
postgresql-cnpg renders one CloudNativePG Cluster per release, with
optional ScheduledBackup and Barman Cloud ObjectStore. Neither chart
renders a password: every credential is a reference to a Secret, and the
render fails when one is unset rather than defaulting.

Applications connect as the owner of their own database, never as
postgres. Extensions that require superuser are created once through
bootstrap.postInitApplicationSQL, which the operator runs during
bootstrap, so no long-lived role holds the privilege.

postgresql-migration runs Flyway as a Job across the databases the
cluster chart created. Both target directories carry no SQL yet -
Sunbird RC and Keycloak each manage their own schema - so the Job
currently skips them and the targets exist as a versioned home.
Imports the sunbird-rc realm from a ConfigMap on first start, with the
realm content checksummed into the pod annotations so a realm change
rolls the pod. An init container waits for the database, standing in for
compose's depends_on: condition: service_healthy.

Keycloak connects as the owner of its own keycloak database rather than
sharing the registry's database as postgres, which is how the compose
stack runs it.

The realm JSON is Sunbird RC's export and still carries its upstream
defaults: a placeholder admin-api client secret, an enabled placeholder
user with a known password, and a wildcard redirect URI on the public
frontend client. All three need hardening before this reaches any
environment that is not local. See the chart README for the client
secret step; the other two are tracked as follow-up.
registry runs Sunbird RC with its Participant schema mounted from a
ConfigMap, and connects as the owner of the registry database rather
than as postgres.

discovery renders the Beckn discovery service, taking its whole DSN from
the CNPG-generated Secret so no password is assembled or escaped in the
chart. It enables readOnlyRootFilesystem, which the other service charts
cannot yet. The Beckn spec is fetched by URL with a cache fallback, and
the render fails when neither a URL nor an existing ConfigMap is set,
since the service refuses to boot without the document.

Both charts fail the render on an unset credential rather than
defaulting one.
Records the chart inventory and the order releases have to go out in,
since the database cluster has to exist before the migration Job and the
services that connect to it.
CI publishes ghcr.io/openagrinet/discovery-service now. It used to build and
scan an image and push nothing, which is the whole reason repository was
empty and the render was made to fail on it.

tag stays empty rather than pinned. Empty falls through to Chart.AppVersion,
so the chart ships pointing at the app version it was written against, and an
environment wanting a different build says so in its own values file. Pinning
it here would only be right once the chart and the app stop versioning
together.

Records that the package is private, which the old comment had no reason to.
A cluster needs a docker-registry secret named in pullSecrets, and without it
the deploy looks clean while the pod sits in ImagePullBackOff -- the same
late-surfacing failure the empty-repository guard exists to prevent.
Establishes the repo-level scaffolding the charts depend on: naming and
structure conventions, a lint script that rebuilds file:// dependencies
before linting, and a CI workflow that runs it. .gitignore excludes Helm
dependency artifacts, which are regenerated rather than committed.
oan-common holds the shared helpers every OAN chart renders through -
names, labels, image references, resources, and security contexts - so
the service charts stay declarative. Resources are deliberately required
rather than defaulted: an unset value fails the render instead of
shipping an unbounded pod.

oan-template is the copy-from starting point for a new service chart.
postgresql-cnpg renders one CloudNativePG Cluster per release, with
optional ScheduledBackup and Barman Cloud ObjectStore. Neither chart
renders a password: every credential is a reference to a Secret, and the
render fails when one is unset rather than defaulting.

Applications connect as the owner of their own database, never as
postgres. Extensions that require superuser are created once through
bootstrap.postInitApplicationSQL, which the operator runs during
bootstrap, so no long-lived role holds the privilege.

postgresql-migration runs Flyway as a Job across the databases the
cluster chart created. Both target directories carry no SQL yet -
Sunbird RC and Keycloak each manage their own schema - so the Job
currently skips them and the targets exist as a versioned home.
Imports the sunbird-rc realm from a ConfigMap on first start, with the
realm content checksummed into the pod annotations so a realm change
rolls the pod. An init container waits for the database, standing in for
compose's depends_on: condition: service_healthy.

Keycloak connects as the owner of its own keycloak database rather than
sharing the registry's database as postgres, which is how the compose
stack runs it.

The realm JSON is Sunbird RC's export and still carries its upstream
defaults: a placeholder admin-api client secret, an enabled placeholder
user with a known password, and a wildcard redirect URI on the public
frontend client. All three need hardening before this reaches any
environment that is not local. See the chart README for the client
secret step; the other two are tracked as follow-up.
registry runs Sunbird RC with its Participant schema mounted from a
ConfigMap, and connects as the owner of the registry database rather
than as postgres.

discovery renders the Beckn discovery service, taking its whole DSN from
the CNPG-generated Secret so no password is assembled or escaped in the
chart. It enables readOnlyRootFilesystem, which the other service charts
cannot yet. The Beckn spec is fetched by URL with a cache fallback, and
the render fails when neither a URL nor an existing ConfigMap is set,
since the service refuses to boot without the document.

Both charts fail the render on an unset credential rather than
defaulting one.
Records the chart inventory and the order releases have to go out in,
since the database cluster has to exist before the migration Job and the
services that connect to it.
CI publishes ghcr.io/openagrinet/discovery-service now. It used to build and
scan an image and push nothing, which is the whole reason repository was
empty and the render was made to fail on it.

tag stays empty rather than pinned. Empty falls through to Chart.AppVersion,
so the chart ships pointing at the app version it was written against, and an
environment wanting a different build says so in its own values file. Pinning
it here would only be right once the chart and the app stop versioning
together.

Records that the package is private, which the old comment had no reason to.
A cluster needs a docker-registry secret named in pullSecrets, and without it
the deploy looks clean while the pod sits in ImagePullBackOff -- the same
late-surfacing failure the empty-repository guard exists to prevent.
Models the network-adapter service from docker-deployment/docker-compose.yml
as a chart on oan-common. The adapter is stateless: its identity is a keypair
in a Secret and everything else comes from the registry.

Config is rendered by an init container from a ConfigMap of placeholders plus
the identity Secret into an emptyDir, so the keypair is never written to a
ConfigMap and never appears in a process environment.

Renders fail outright on the five values whose absence would otherwise produce
a pod that starts, reports Ready and does not work. HPA, PodDisruptionBudget,
Ingress and the upstream readiness gates are all optional and off by default;
ci/otel-ingress-values.yaml renders that other branch so lint covers it.
The chart shipped with an empty tag, which renders an image reference with no
tag at all. latest keeps the draft chart installable while the discovery
service has no released version to pin to.
@KrutikaPhirangi
KrutikaPhirangi changed the base branch from development to feat/4-docker-compose September 8, 2026 05:15
Renames network-adapter to adapter-service and makes the role a value. The
provider, network and experience adapters are the same image and the same
config format, so what differed between them was configuration, not three
charts: role now selects the handler role, the step list and the routing
target.

experience is the role that justifies the abstraction. It sits inside the
trust boundary and accepts unsigned requests, so it runs as bap with no
validateSign step, while the other two receive from the network and must
verify first. Its example sets both explicitly rather than relying on the
default, so the missing step reads as deliberate.

discovery.url becomes routing.rules, a list, because provider fans out to
several upstreams and a single target could not express that. Every rule is
checked at render time for a target url, a missing trailing slash and a
non-empty endpoint list.

Each example sets fullnameOverride to <role>-adapter. Without it fullname
resolves to "<release>-adapter-service", so a release named network-adapter
produced a Service called network-adapter-adapter-service and the experience
adapter's route to network-adapter:9201 resolved to nothing. That only worked
before because the chart itself was named network-adapter.

ci/otel-ingress-values.yaml now renders the experience role, so CI covers the
role branches that ci/lint-values.yaml (network) does not reach.

BREAKING CHANGE: chart renamed network-adapter -> adapter-service; role is
required with no default; discovery.url replaced by routing.rules; config
placeholders renamed __NETWORK_* -> __ADAPTER_*; routing file is now
routing-<role>.yaml.
@manjudr

manjudr commented Sep 8, 2026

Copy link
Copy Markdown
Member

Code review

Found 1 issue:

  1. adapter-service's probes never render. values.yaml defines probes under a nested probes.liveness / probes.readiness shape, but the shared oan-common.probes helper only reads flat .Values.livenessProbe / .Values.readinessProbe / .Values.startupProbe. Helm returns nil for the undefined flat keys rather than erroring, so the and guard is false and the probe blocks are silently omitted from the rendered Deployment — the chart ships with no liveness or readiness probes despite values.yaml appearing to configure them.

# 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
path: /health
initialDelaySeconds: 10
periodSeconds: 20
timeoutSeconds: 3
failureThreshold: 3
readiness:
enabled: true
path: /health
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
autoscaling:

{{- define "oan-common.probes" -}}
{{- if and .Values.startupProbe .Values.startupProbe.enabled }}
startupProbe:
{{- include "oan-common.probeSpec" (dict "probe" .Values.startupProbe "name" "startupProbe" "chart" .Chart.Name) | nindent 2 }}
{{- end }}
{{- if and .Values.livenessProbe .Values.livenessProbe.enabled }}
livenessProbe:
{{- include "oan-common.probeSpec" (dict "probe" .Values.livenessProbe "name" "livenessProbe" "chart" .Chart.Name) | nindent 2 }}
{{- end }}
{{- if and .Values.readinessProbe .Values.readinessProbe.enabled }}
readinessProbe:
{{- include "oan-common.probeSpec" (dict "probe" .Values.readinessProbe "name" "readinessProbe" "chart" .Chart.Name) | nindent 2 }}
{{- end }}

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@manjudr
manjudr merged commit d2728cb into feat/4-docker-compose Sep 8, 2026
1 check passed
@KrutikaPhirangi
KrutikaPhirangi deleted the feat/53-chart-scaffold-common-library branch September 9, 2026 07:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants