diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b28ef922..0f3efd53 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -39,6 +39,7 @@ jobs: --charts charts/ingress-controller \ --charts charts/aisix-cp \ --charts charts/aisix \ + --charts charts/developer-portal-fe \ --charts charts/ngxdig' - name: Verify Chart.lock files diff --git a/AGENTS.md b/AGENTS.md index 00d2e5bf..7fe97636 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -60,7 +60,7 @@ Operational rule: **on any `release/*` branch, only bump the charts you actually - `CR_SKIP_EXISTING: true`: already-released versions are skipped and the index is merged (not overwritten), so multiple branches are safe. - the GitHub Release tag is `-` (e.g. `gateway-3.9.0`); the numbering rule keeps it unique across lines. - `ci.yaml` (lint / ct install / helm-docs) also covers `release/**`. -- after editing a chart, regenerate each chart's `README.md` with `helm-docs --chart-search-root=charts`, or the helm-docs CI check fails. +- after editing a chart, regenerate each chart's `README.md` with `helm-docs --chart-search-root=charts`, or the helm-docs CI check fails. Use **v1.13.1**, the version CI installs: 1.14.x appends an `Autogenerated from chart metadata...` footer that CI then rejects, and the `helm-docs` pre-commit hook runs whatever `helm-docs` is on `PATH` — so it happily rewrites every chart README into a form CI fails on. Put a v1.13.1 binary first on `PATH` when committing. ## Gateway chart: which config to expose @@ -73,6 +73,12 @@ The gateway chart's `values.yaml`/configmap mirror the gateway's `conf/config-de When adding values, keep defaults identical to `config-default.yaml` so a default render changes nothing. To verify a rendered config is valid, extract `config.yaml` from the rendered configmap and run it through the real image: `docker run --rm --entrypoint sh -v $PWD/config.yaml:/usr/local/apisix/conf/config.yaml api7/api7-ee-3-gateway: -c 'apisix init'` — the config schema is validated at init. +## developer-portal-fe chart: config is a pass-through + +`config.yaml` is rendered from `developerPortal.config`, which carries the application's whole config schema. **Do not add per-field `.Values` keys for it** — the schema lives in `api7/api7ee-developer-portal` (`apps/site/src/lib/config/schema.ts`) and mirroring it drifts on every application release. Only the connection settings the chart itself owns are templated, and they are merged over the block so a user cannot move the Secret-backed `${PORTAL_TOKEN}` / `${DB_URL}` / `${AUTH_SECRET}` placeholders into the ConfigMap. + +To verify a rendered config, run it through the image's own loader: `docker run --rm -v $PWD/config.yaml:/app/apps/site/config.yaml -e PORTAL_TOKEN=x -e DB_URL=x -e AUTH_SECRET=$(head -c 32 /dev/zero | tr '\0' a) --entrypoint sh api7/api7-ee-developer-portal-fe: -c 'node preflight.js'` — reaching the `Portal URL:` line means the config validated (the portal/database checks after it need a live backend). That mount path is fixed: the standalone server chdirs into `apps/site`, and preflight looks there. + ## User upgrade guidance - Follow the latest line: `helm repo update && helm upgrade api7/` (picks the highest version, always the newest EE line). diff --git a/charts/developer-portal-fe/.helmignore b/charts/developer-portal-fe/.helmignore index 0e8a0eb3..879b3670 100644 --- a/charts/developer-portal-fe/.helmignore +++ b/charts/developer-portal-fe/.helmignore @@ -21,3 +21,5 @@ .idea/ *.tmproj .vscode/ +README.md.gotmpl +ci/ diff --git a/charts/developer-portal-fe/Chart.yaml b/charts/developer-portal-fe/Chart.yaml index fc992f66..b0a564d7 100644 --- a/charts/developer-portal-fe/Chart.yaml +++ b/charts/developer-portal-fe/Chart.yaml @@ -2,8 +2,8 @@ apiVersion: v2 name: developer-portal-fe description: A Helm chart for API7 Developer Portal Frontend type: application -version: 0.1.0 -appVersion: "0.5.7" +version: 0.2.0 +appVersion: "0.14.0" maintainers: - name: API7 email: support@api7.ai diff --git a/charts/developer-portal-fe/README.md b/charts/developer-portal-fe/README.md index 77891072..332c9442 100644 --- a/charts/developer-portal-fe/README.md +++ b/charts/developer-portal-fe/README.md @@ -1,6 +1,6 @@ # developer-portal-fe -![Version: 0.1.0](https://img.shields.io/badge/Version-0.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.5.7](https://img.shields.io/badge/AppVersion-0.5.7-informational?style=flat-square) +![Version: 0.2.0](https://img.shields.io/badge/Version-0.2.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 0.14.0](https://img.shields.io/badge/AppVersion-0.14.0-informational?style=flat-square) A Helm chart for API7 Developer Portal Frontend @@ -16,6 +16,107 @@ A Helm chart for API7 Developer Portal Frontend |------------|------|---------| | https://charts.bitnami.com/bitnami | postgresql | 12.12.10 | +## Install + +```sh +helm repo add api7 https://charts.api7.ai +helm repo update + +helm install developer-portal-fe api7/developer-portal-fe --namespace api7 --create-namespace +``` + +## Configuring the portal application + +The chart renders the application's `config.yaml` from `developerPortal.config`: +the block is copied into the file as it is written, and the connection settings +below are then merged over it. Any field the application's config schema accepts +can be set there — `app.name`, `app.desc`, `auth.emailAndPassword`, +`auth.genericOAuthProviders`, `db.ssl`, and so on — without waiting for a chart +release. Which fields exist depends on the deployed application version +(`developerPortal.image.tag`). + +These are the paths the chart owns. They are merged in last, so they always win +over `developerPortal.config` and setting them there has no effect: + +| Config path | Comes from | +| :--- | :--- | +| `portal.url` | `portal.url` | +| `portal.token` | `${PORTAL_TOKEN}` — Secret (`portal.existingSecret`) | +| `db.url` | `${DB_URL}` — Secret (`db.existingSecret`) | +| `auth.secret` | `${AUTH_SECRET}` — Secret (`auth.existingSecret`) | +| `app.baseURL`, `app.trustedOrigins` | `app.baseURL`, `app.trustedOrigins` | + +Changing `developerPortal.config` rolls the Pods automatically — the Deployment +carries a checksum of the rendered ConfigMap. + +### Keep credentials out of the ConfigMap + +`developerPortal.config` is rendered into a ConfigMap, so it is the wrong place +for a client secret or a database password. The application substitutes +`${VAR}` and `${VAR:default}` in every string value of `config.yaml` at startup, +so put a placeholder in the config and inject the real value as an environment +variable with `developerPortal.extraEnvVars`: + +```yaml +developerPortal: + config: + auth: + genericOAuthProviders: + - providerId: keycloak + discoveryUrl: https://sso.example.com/realms/main/.well-known/openid-configuration + clientId: devportal + clientSecret: ${OIDC_CLIENT_SECRET} + extraEnvVars: + - name: OIDC_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: devportal-oidc + key: client-secret +``` + +A missing variable is a startup error, not an empty string — the Pod fails +fast instead of running with a blank secret. + +### Examples + +Name and describe the portal: + +```yaml +developerPortal: + config: + app: + name: "Example APIs" + desc: "Everything you need to build on Example." +``` + +Turn off password sign-in and require two-factor authentication: + +```yaml +developerPortal: + config: + auth: + emailAndPassword: + enabled: false + twoFactor: + enabled: true + required: true +``` + +Connect to a managed PostgreSQL that requires TLS, in its own schema: + +```yaml +developerPortal: + config: + db: + schema: portal + ssl: + rejectUnauthorized: true + ca: ${DB_CA_PEM} + pool: + max: 30 + min: 2 +``` + ## Values | Key | Type | Default | Description | @@ -29,12 +130,13 @@ A Helm chart for API7 Developer Portal Frontend | db.existingSecret | string | `""` | | | db.existingSecretKey | string | `"db-url"` | | | db.url | string | `"postgres://portal:portal123@developer-portal-fe-postgresql:5432/portal"` | | +| developerPortal.config | object | `{"app":{"name":"Developer Portal"}}` | Developer Portal application config, copied into `config.yaml` with the chart-owned connection settings merged over it | | developerPortal.extraEnvVars | list | `[]` | | | developerPortal.extraVolumeMounts | list | `[]` | | | developerPortal.extraVolumes | list | `[]` | | | developerPortal.image.pullPolicy | string | `"IfNotPresent"` | | | developerPortal.image.repository | string | `"api7/api7-ee-developer-portal-fe"` | | -| developerPortal.image.tag | string | `"v0.5.7"` | | +| developerPortal.image.tag | string | `"v0.14.0"` | | | developerPortal.livenessProbe.failureThreshold | int | `10` | | | developerPortal.livenessProbe.initialDelaySeconds | int | `30` | | | developerPortal.livenessProbe.path | string | `"/"` | | @@ -89,3 +191,15 @@ A Helm chart for API7 Developer Portal Frontend | tolerations | list | `[]` | | | topologySpreadConstraints | list | `[]` | | +## Upgrading + +### To 0.2.0 + +The bundled application moves from 0.5.7 to 0.14.0, which requires `auth.secret` +to be at least 32 characters — a shorter secret fails validation at startup. +Rotate it before upgrading (`openssl rand -base64 32`); rotating signs existing +users out, so they will have to sign in again. + +`auth.socialProviders` is the one config field to avoid on this application +version: its schema is mis-declared upstream and the Pod fails to start when the +field is set. Use `auth.genericOAuthProviders` instead. diff --git a/charts/developer-portal-fe/README.md.gotmpl b/charts/developer-portal-fe/README.md.gotmpl new file mode 100644 index 00000000..84f3352e --- /dev/null +++ b/charts/developer-portal-fe/README.md.gotmpl @@ -0,0 +1,125 @@ +{{ template "chart.header" . }} + +{{ template "chart.badgesSection" . }} + +{{ template "chart.description" . }} + +{{ template "chart.maintainersSection" . }} + +{{ template "chart.requirementsSection" . }} + +## Install + +```sh +helm repo add api7 https://charts.api7.ai +helm repo update + +helm install developer-portal-fe api7/developer-portal-fe --namespace api7 --create-namespace +``` + +## Configuring the portal application + +The chart renders the application's `config.yaml` from `developerPortal.config`: +the block is copied into the file as it is written, and the connection settings +below are then merged over it. Any field the application's config schema accepts +can be set there — `app.name`, `app.desc`, `auth.emailAndPassword`, +`auth.genericOAuthProviders`, `db.ssl`, and so on — without waiting for a chart +release. Which fields exist depends on the deployed application version +(`developerPortal.image.tag`). + +These are the paths the chart owns. They are merged in last, so they always win +over `developerPortal.config` and setting them there has no effect: + +| Config path | Comes from | +| :--- | :--- | +| `portal.url` | `portal.url` | +| `portal.token` | `${PORTAL_TOKEN}` — Secret (`portal.existingSecret`) | +| `db.url` | `${DB_URL}` — Secret (`db.existingSecret`) | +| `auth.secret` | `${AUTH_SECRET}` — Secret (`auth.existingSecret`) | +| `app.baseURL`, `app.trustedOrigins` | `app.baseURL`, `app.trustedOrigins` | + +Changing `developerPortal.config` rolls the Pods automatically — the Deployment +carries a checksum of the rendered ConfigMap. + +### Keep credentials out of the ConfigMap + +`developerPortal.config` is rendered into a ConfigMap, so it is the wrong place +for a client secret or a database password. The application substitutes +`${VAR}` and `${VAR:default}` in every string value of `config.yaml` at startup, +so put a placeholder in the config and inject the real value as an environment +variable with `developerPortal.extraEnvVars`: + +```yaml +developerPortal: + config: + auth: + genericOAuthProviders: + - providerId: keycloak + discoveryUrl: https://sso.example.com/realms/main/.well-known/openid-configuration + clientId: devportal + clientSecret: ${OIDC_CLIENT_SECRET} + extraEnvVars: + - name: OIDC_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: devportal-oidc + key: client-secret +``` + +A missing variable is a startup error, not an empty string — the Pod fails +fast instead of running with a blank secret. + +### Examples + +Name and describe the portal: + +```yaml +developerPortal: + config: + app: + name: "Example APIs" + desc: "Everything you need to build on Example." +``` + +Turn off password sign-in and require two-factor authentication: + +```yaml +developerPortal: + config: + auth: + emailAndPassword: + enabled: false + twoFactor: + enabled: true + required: true +``` + +Connect to a managed PostgreSQL that requires TLS, in its own schema: + +```yaml +developerPortal: + config: + db: + schema: portal + ssl: + rejectUnauthorized: true + ca: ${DB_CA_PEM} + pool: + max: 30 + min: 2 +``` + +{{ template "chart.valuesSection" . }} + +## Upgrading + +### To 0.2.0 + +The bundled application moves from 0.5.7 to 0.14.0, which requires `auth.secret` +to be at least 32 characters — a shorter secret fails validation at startup. +Rotate it before upgrading (`openssl rand -base64 32`); rotating signs existing +users out, so they will have to sign in again. + +`auth.socialProviders` is the one config field to avoid on this application +version: its schema is mis-declared upstream and the Pod fails to start when the +field is set. Use `auth.genericOAuthProviders` instead. diff --git a/charts/developer-portal-fe/ci/full-config-values.yaml b/charts/developer-portal-fe/ci/full-config-values.yaml new file mode 100644 index 00000000..88dbef12 --- /dev/null +++ b/charts/developer-portal-fe/ci/full-config-values.yaml @@ -0,0 +1,76 @@ +# Renders every part of the Developer Portal config surface through +# developerPortal.config, so a template change that breaks the pass-through is +# caught by `ct lint`. Credentials are ${ENV} placeholders on purpose — that is +# the documented way to keep them out of the ConfigMap. +portal: + url: "https://api7-developer-portal:4321" + +app: + baseURL: "https://portal.example.com" + trustedOrigins: + - "https://portal.example.com" + +developerPortal: + config: + db: + schema: portal + pool: + max: 30 + min: 2 + idleTimeout: 60000 + connectionTimeout: 5000 + allowExitOnIdle: false + ssl: + rejectUnauthorized: true + ca: ${DB_CA_PEM} + auth: + adminUserIds: + - usr_admin + session: + expiresIn: 604800 + updateAge: 86400 + emailAndPassword: + enabled: false + requireEmailVerification: true + revokeSessionsOnPasswordReset: true + twoFactor: + enabled: true + required: true + # auth.socialProviders is deliberately absent: the application's own + # schema mis-declares it and fails to parse a config that sets it + # (fixed after 0.14.0). genericOAuthProviders covers the same ground. + genericOAuthProviders: + - providerId: keycloak + discoveryUrl: https://sso.example.com/realms/main/.well-known/openid-configuration + clientId: devportal + clientSecret: ${OIDC_CLIENT_SECRET} + scopes: ["openid", "profile", "email"] + sso: + providers: + - providerId: keycloak + domains: + - '@example\.com$' + app: + name: "Example APIs" + desc: "Everything you need to build on Example." + signUpConsentLabel: 'I agree to the Terms.' + applicationDetail: + subscriptions: true + usage: false + credentialsTabs: + keyAuth: true + basicAuth: false + oauth: false + apiHub: + enabled: true + extraEnvVars: + - name: DB_CA_PEM + valueFrom: + secretKeyRef: + name: devportal-db-ca + key: ca.crt + - name: OIDC_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: devportal-oidc + key: client-secret diff --git a/charts/developer-portal-fe/templates/configmap.yaml b/charts/developer-portal-fe/templates/configmap.yaml index 41cf3505..127b7ace 100644 --- a/charts/developer-portal-fe/templates/configmap.yaml +++ b/charts/developer-portal-fe/templates/configmap.yaml @@ -6,23 +6,18 @@ metadata: labels: {{- include "developer-portal-fe.labels" . | nindent 4 }} data: + {{- /* + developerPortal.config passes the whole application config schema through. + The connection settings the chart owns are merged last so they win over + anything set there — in particular the ${...} placeholders that keep the + portal token, database URL and auth secret in a Secret instead of here. + deepCopy is required: mergeOverwrite mutates its first argument in place. + */}} + {{- $owned := dict + "portal" (dict "url" .Values.portal.url "token" "${PORTAL_TOKEN}") + "db" (dict "url" "${DB_URL}") + "auth" (dict "secret" "${AUTH_SECRET}") + "app" (dict "baseURL" .Values.app.baseURL + "trustedOrigins" .Values.app.trustedOrigins) }} config.yaml: | - # Portal API connection configuration - portal: - url: {{ .Values.portal.url | quote }} - token: ${PORTAL_TOKEN} - - # Database configuration - db: - url: ${DB_URL} - - # Authentication configuration - auth: - secret: ${AUTH_SECRET} - - # Application configuration - app: - name: "Developer Portal" - baseURL: {{ .Values.app.baseURL | quote }} - trustedOrigins: - {{- toYaml .Values.app.trustedOrigins | nindent 8 }} + {{- toYaml (mergeOverwrite (deepCopy (.Values.developerPortal.config | default dict)) $owned) | nindent 4 }} diff --git a/charts/developer-portal-fe/values.yaml b/charts/developer-portal-fe/values.yaml index e32744c6..bb748638 100644 --- a/charts/developer-portal-fe/values.yaml +++ b/charts/developer-portal-fe/values.yaml @@ -9,7 +9,38 @@ developerPortal: image: repository: api7/api7-ee-developer-portal-fe pullPolicy: IfNotPresent - tag: "v0.5.7" + tag: "v0.14.0" + + # Any field the application's config schema accepts can be set here, so new + # application config does not need a chart release. See the chart README for + # the fields in common use and for complete examples. + # + # The connection settings the chart owns are merged in last and always win, + # so setting them here has no effect: portal.url (from portal.url), + # portal.token / db.url / auth.secret (Secret-backed ${...} placeholders), + # app.baseURL and app.trustedOrigins (from app.baseURL / app.trustedOrigins). + # + # Keep credentials OUT of this block — it renders into a ConfigMap. Every + # string value supports ${VAR} / ${VAR:default} substitution at startup, so + # reference a placeholder here and inject the real value through + # developerPortal.extraEnvVars below. + # -- Developer Portal application config, copied into `config.yaml` with the chart-owned connection settings merged over it + config: + app: + name: "Developer Portal" + # desc: "Explore and integrate with our APIs." + # auth: + # emailAndPassword: + # enabled: true + # requireEmailVerification: false + # genericOAuthProviders: + # - providerId: keycloak + # discoveryUrl: https://sso.example.com/realms/main/.well-known/openid-configuration + # clientId: devportal + # clientSecret: ${OIDC_CLIENT_SECRET} + # db: + # schema: portal + # ssl: true # Resource limits and requests resources: {}