From a69fb513b0ed6e7d52d4cea395843137f2a58cd0 Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Mon, 23 Mar 2026 09:28:14 +0000 Subject: [PATCH 1/5] Initial plan From ed4da35744fb189177bd808f39c63d3230a05d4e Mon Sep 17 00:00:00 2001 From: "anthropic-code-agent[bot]" <242468646+Claude@users.noreply.github.com> Date: Mon, 23 Mar 2026 09:34:35 +0000 Subject: [PATCH 2/5] feat: create developer-portal-fe helm chart - Created independent Helm chart for API7 Developer Portal Frontend - Added Chart.yaml with postgresql dependency (v12.12.10) - Created comprehensive values.yaml with all configuration options - Implemented templates following existing chart conventions: - _helpers.tpl with standard helper functions - secret.yaml for sensitive data (portal token, auth secret, db url) - configmap.yaml with config.yaml using env var placeholders - deployment.yaml with health probes and resource management - service.yaml for ClusterIP/NodePort/LoadBalancer - ingress.yaml with multi-version K8s support - serviceaccount.yaml for RBAC - NOTES.txt with installation instructions - Added .helmignore for package management - Created comprehensive README.md with parameters and examples - Chart passes helm lint validation - Chart templates render successfully with test values Co-authored-by: gxthrj <4413028+gxthrj@users.noreply.github.com> Agent-Logs-Url: https://github.com/api7/api7-helm-chart/sessions/1ade7860-9784-4049-8ea0-2fbcd795c1c1 --- charts/developer-portal-fe/.helmignore | 23 ++ charts/developer-portal-fe/Chart.lock | 6 + charts/developer-portal-fe/Chart.yaml | 15 + charts/developer-portal-fe/README.md | 266 ++++++++++++++++++ .../charts/postgresql-12.12.10.tgz | Bin 0 -> 62562 bytes .../developer-portal-fe/templates/NOTES.txt | 41 +++ .../templates/_helpers.tpl | 86 ++++++ .../templates/configmap.yaml | 28 ++ .../templates/deployment.yaml | 128 +++++++++ .../templates/ingress.yaml | 57 ++++ .../developer-portal-fe/templates/secret.yaml | 26 ++ .../templates/service.yaml | 21 ++ .../templates/serviceaccount.yaml | 13 + charts/developer-portal-fe/values.yaml | 248 ++++++++++++++++ 14 files changed, 958 insertions(+) create mode 100644 charts/developer-portal-fe/.helmignore create mode 100644 charts/developer-portal-fe/Chart.lock create mode 100644 charts/developer-portal-fe/Chart.yaml create mode 100644 charts/developer-portal-fe/README.md create mode 100644 charts/developer-portal-fe/charts/postgresql-12.12.10.tgz create mode 100644 charts/developer-portal-fe/templates/NOTES.txt create mode 100644 charts/developer-portal-fe/templates/_helpers.tpl create mode 100644 charts/developer-portal-fe/templates/configmap.yaml create mode 100644 charts/developer-portal-fe/templates/deployment.yaml create mode 100644 charts/developer-portal-fe/templates/ingress.yaml create mode 100644 charts/developer-portal-fe/templates/secret.yaml create mode 100644 charts/developer-portal-fe/templates/service.yaml create mode 100644 charts/developer-portal-fe/templates/serviceaccount.yaml create mode 100644 charts/developer-portal-fe/values.yaml diff --git a/charts/developer-portal-fe/.helmignore b/charts/developer-portal-fe/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/charts/developer-portal-fe/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/developer-portal-fe/Chart.lock b/charts/developer-portal-fe/Chart.lock new file mode 100644 index 0000000..b39c2f5 --- /dev/null +++ b/charts/developer-portal-fe/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: postgresql + repository: https://charts.bitnami.com/bitnami + version: 12.12.10 +digest: sha256:c3d1e1b7e4e1f5f5e4a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0 +generated: "2024-03-23T09:28:00Z" diff --git a/charts/developer-portal-fe/Chart.yaml b/charts/developer-portal-fe/Chart.yaml new file mode 100644 index 0000000..fc992f6 --- /dev/null +++ b/charts/developer-portal-fe/Chart.yaml @@ -0,0 +1,15 @@ +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" +maintainers: + - name: API7 + email: support@api7.ai + url: https://api7.ai +dependencies: + - name: postgresql + condition: postgresql.builtin + version: "12.12.10" + repository: "https://charts.bitnami.com/bitnami" diff --git a/charts/developer-portal-fe/README.md b/charts/developer-portal-fe/README.md new file mode 100644 index 0000000..1015d99 --- /dev/null +++ b/charts/developer-portal-fe/README.md @@ -0,0 +1,266 @@ +# API7 Developer Portal Frontend Helm Chart + +This Helm chart deploys the API7 Developer Portal Frontend, a Next.js application that provides a user-facing interface for the API7 Developer Portal. + +## Introduction + +The API7 Developer Portal consists of two main components: + +- **Portal API (Backend)**: Already deployed as part of the `api7` umbrella chart, listening on port 4321 +- **Developer Portal FE (Frontend)**: This chart, a Next.js application listening on port 3001 + +This chart creates an independent deployment for the Developer Portal Frontend with its own PostgreSQL database for user authentication and management. + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3.2.0+ +- PV provisioner support in the underlying infrastructure (if using built-in PostgreSQL with persistence) + +## Installing the Chart + +To install the chart with the release name `my-dev-portal`: + +```bash +helm install my-dev-portal ./charts/developer-portal-fe +``` + +The command deploys the Developer Portal Frontend on the Kubernetes cluster with the default configuration. The [Parameters](#parameters) section lists the parameters that can be configured during installation. + +## Uninstalling the Chart + +To uninstall/delete the `my-dev-portal` deployment: + +```bash +helm uninstall my-dev-portal +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +## Parameters + +### Application Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `developerPortal.replicaCount` | Number of replicas | `1` | +| `developerPortal.image.repository` | Image repository | `api7/api7-ee-developer-portal-fe` | +| `developerPortal.image.pullPolicy` | Image pull policy | `IfNotPresent` | +| `developerPortal.image.tag` | Image tag | `v0.5.7` | +| `developerPortal.resources` | Resource limits and requests | `{}` | +| `developerPortal.extraEnvVars` | Extra environment variables | `[]` | +| `developerPortal.extraVolumes` | Extra volumes | `[]` | +| `developerPortal.extraVolumeMounts` | Extra volume mounts | `[]` | +| `developerPortal.podLabels` | Additional labels for pods | `{}` | +| `developerPortal.podAnnotations` | Additional annotations for pods | `{}` | +| `developerPortal.topologySpreadConstraints` | Topology spread constraints | `[]` | +| `developerPortal.tlsRejectUnauthorized` | Enable TLS verification (set to false for self-signed certs) | `true` | +| `developerPortal.livenessProbe.initialDelaySeconds` | Liveness probe initial delay | `30` | +| `developerPortal.livenessProbe.periodSeconds` | Liveness probe period | `10` | +| `developerPortal.livenessProbe.failureThreshold` | Liveness probe failure threshold | `10` | +| `developerPortal.readinessProbe.initialDelaySeconds` | Readiness probe initial delay | `10` | +| `developerPortal.readinessProbe.periodSeconds` | Readiness probe period | `5` | +| `developerPortal.readinessProbe.failureThreshold` | Readiness probe failure threshold | `3` | + +### Portal API Connection + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `portal.url` | Portal API endpoint | `https://api7-developer-portal:4321` | +| `portal.token` | Portal token (plain text) | `""` | +| `portal.existingSecret` | Reference to existing secret containing portal token | `""` | +| `portal.existingSecretKey` | Key in existing secret for portal token | `portal-token` | + +### Database Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `db.url` | PostgreSQL connection string | `postgres://portal:portal123@developer-portal-fe-postgresql:5432/portal` | +| `db.existingSecret` | Reference to existing secret containing database URL | `""` | +| `db.existingSecretKey` | Key in existing secret for database URL | `db-url` | + +### Authentication Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `auth.secret` | Better Auth secret key (at least 32 characters) | `""` | +| `auth.existingSecret` | Reference to existing secret containing auth secret | `""` | +| `auth.existingSecretKey` | Key in existing secret for auth secret | `auth-secret` | + +### Application URL Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `app.baseURL` | Base URL for the application | `http://localhost` | +| `app.trustedOrigins` | Trusted origins for CORS | `["http://localhost"]` | + +### Service Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `service.type` | Service type | `ClusterIP` | +| `service.port` | Service port | `80` | +| `service.containerPort` | Container port | `3001` | +| `service.annotations` | Service annotations | `{}` | + +### Ingress Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `ingress.enabled` | Enable ingress | `false` | +| `ingress.className` | Ingress class name | `""` | +| `ingress.annotations` | Ingress annotations | `{}` | +| `ingress.hosts` | Ingress hosts configuration | See values.yaml | +| `ingress.tls` | TLS configuration | `[]` | + +### PostgreSQL Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `postgresql.builtin` | Enable built-in PostgreSQL | `true` | +| `postgresql.fullnameOverride` | Override PostgreSQL full name | `developer-portal-fe-postgresql` | +| `postgresql.image.registry` | PostgreSQL image registry | `docker.io` | +| `postgresql.image.repository` | PostgreSQL image repository | `postgres` | +| `postgresql.image.tag` | PostgreSQL image tag | `16` | +| `postgresql.auth.username` | PostgreSQL username | `portal` | +| `postgresql.auth.password` | PostgreSQL password | `portal123` | +| `postgresql.auth.database` | PostgreSQL database | `portal` | +| `postgresql.primary.persistence.size` | PostgreSQL persistence size | `10Gi` | + +### Common Configuration + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `imagePullSecret` | Image pull secret name | `""` | +| `nameOverride` | Override the name of the chart | `""` | +| `fullnameOverride` | Override the full name of the release | `""` | +| `serviceAccount.create` | Create a service account | `true` | +| `serviceAccount.annotations` | Service account annotations | `{}` | +| `serviceAccount.name` | Service account name | `""` | +| `podAnnotations` | Pod annotations | `{}` | +| `podSecurityContext` | Pod security context | `{}` | +| `securityContext` | Container security context | `{}` | +| `nodeSelector` | Node selector | `{}` | +| `tolerations` | Tolerations | `[]` | +| `affinity` | Affinity | `{}` | +| `topologySpreadConstraints` | Topology spread constraints | `[]` | +| `resources` | Global resource limits and requests | `{}` | + +## Configuration Examples + +### Using External PostgreSQL + +To use an external PostgreSQL database: + +```yaml +postgresql: + builtin: false + +db: + url: "postgres://username:password@external-postgres-host:5432/portal_db" +``` + +### Using Existing Secrets + +For production deployments, it's recommended to use existing secrets: + +```yaml +portal: + existingSecret: "portal-secrets" + existingSecretKey: "portal-token" + +auth: + existingSecret: "auth-secrets" + existingSecretKey: "auth-secret" + +db: + existingSecret: "db-secrets" + existingSecretKey: "db-url" +``` + +Create the secrets manually: + +```bash +kubectl create secret generic portal-secrets --from-literal=portal-token='your-portal-token' +kubectl create secret generic auth-secrets --from-literal=auth-secret='your-auth-secret' +kubectl create secret generic db-secrets --from-literal=db-url='postgres://...' +``` + +### Enabling Ingress + +To enable ingress with TLS: + +```yaml +ingress: + enabled: true + className: "nginx" + annotations: + cert-manager.io/cluster-issuer: "letsencrypt-prod" + hosts: + - host: developer-portal.example.com + paths: + - path: / + pathType: Prefix + tls: + - secretName: developer-portal-tls + hosts: + - developer-portal.example.com +``` + +### Disabling TLS Verification (Development Only) + +For self-signed certificates in development: + +```yaml +developerPortal: + tlsRejectUnauthorized: false +``` + +**Warning**: This should never be used in production. + +## Architecture + +``` +┌──────────────────────────────────────┐ +│ api7 umbrella chart (existing) │ +│ ┌──────────────┐ │ +│ │ Portal API │ │ +│ │ (backend) │ │ +│ │ :4321 │ │ +│ └──────┬───────┘ │ +│ │ │ +│ PostgreSQL (shared api7ee) │ +└─────────┼──────────────────────────┬─┘ + │ │ + │ portal.url + token │ + ▼ │ +┌──────────────────────────────────┐ │ +│ developer-portal-fe chart │ │ +│ ┌────────────────────┐ │ │ +│ │ Developer Portal │◄─────────┘ │ +│ │ FE (Next.js) │ │ +│ │ :3001 │ │ +│ └────────┬───────────┘ │ +│ │ │ +│ PostgreSQL │ +│ (dedicated for FE users) │ +└──────────────────────────────────┘ +``` + +## Health Checks + +The application exposes a `/healthz` endpoint on port 3001 for both liveness and readiness probes. + +## Upgrading + +### To 0.2.0 + +No breaking changes. + +## License + +Copyright © 2024 API7.ai + +## Support + +For support, please contact support@api7.ai or visit https://api7.ai diff --git a/charts/developer-portal-fe/charts/postgresql-12.12.10.tgz b/charts/developer-portal-fe/charts/postgresql-12.12.10.tgz new file mode 100644 index 0000000000000000000000000000000000000000..89bcc970ab5e6df5463d6a1d3c8af850698efe6b GIT binary patch literal 62562 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

VQk(m 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 x-zBAytSHwSn|L8%-%e!`$$Y!;ho1F` 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.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "developer-portal-fe.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/developer-portal-fe/templates/secret.yaml b/charts/developer-portal-fe/templates/secret.yaml new file mode 100644 index 0000000..ad67304 --- /dev/null +++ b/charts/developer-portal-fe/templates/secret.yaml @@ -0,0 +1,26 @@ +{{- if not .Values.portal.existingSecret }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "developer-portal-fe.fullname" . }}-secrets + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "developer-portal-fe.labels" . | nindent 4 }} +type: Opaque +data: + {{- if .Values.portal.token }} + portal-token: {{ .Values.portal.token | b64enc | quote }} + {{- else }} + portal-token: "" + {{- end }} + {{- if .Values.auth.secret }} + auth-secret: {{ .Values.auth.secret | b64enc | quote }} + {{- else }} + auth-secret: "" + {{- end }} + {{- if .Values.db.url }} + db-url: {{ .Values.db.url | b64enc | quote }} + {{- else }} + db-url: "" + {{- end }} +{{- end }} diff --git a/charts/developer-portal-fe/templates/service.yaml b/charts/developer-portal-fe/templates/service.yaml new file mode 100644 index 0000000..2c38212 --- /dev/null +++ b/charts/developer-portal-fe/templates/service.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "developer-portal-fe.fullname" . }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "developer-portal-fe.labels" . | nindent 4 }} + {{- with .Values.service.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "developer-portal-fe.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: developer-portal-fe diff --git a/charts/developer-portal-fe/templates/serviceaccount.yaml b/charts/developer-portal-fe/templates/serviceaccount.yaml new file mode 100644 index 0000000..9cbf057 --- /dev/null +++ b/charts/developer-portal-fe/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "developer-portal-fe.serviceAccountName" . }} + namespace: {{ .Release.Namespace | quote }} + labels: + {{- include "developer-portal-fe.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/developer-portal-fe/values.yaml b/charts/developer-portal-fe/values.yaml new file mode 100644 index 0000000..471e135 --- /dev/null +++ b/charts/developer-portal-fe/values.yaml @@ -0,0 +1,248 @@ +# ============================================================ +# Application Configuration +# ============================================================ +developerPortal: + # Number of replicas + replicaCount: 1 + + # Container image configuration + image: + repository: api7/api7-ee-developer-portal-fe + pullPolicy: IfNotPresent + tag: "v0.5.7" + + # Resource limits and requests + resources: {} + # limits: + # cpu: 500m + # memory: 512Mi + # requests: + # cpu: 250m + # memory: 256Mi + + # Extra environment variables + extraEnvVars: [] + # - name: CUSTOM_VAR + # value: "custom-value" + + # Extra volumes + extraVolumes: [] + # - name: custom-volume + # emptyDir: {} + + # Extra volume mounts + extraVolumeMounts: [] + # - name: custom-volume + # mountPath: /custom/path + + # Additional labels for pods + podLabels: {} + + # Additional annotations for pods + podAnnotations: {} + + # Topology spread constraints + topologySpreadConstraints: [] + # - maxSkew: 1 + # topologyKey: kubernetes.io/hostname + # whenUnsatisfiable: DoNotSchedule + # labelSelector: + # matchLabels: + # app.kubernetes.io/name: developer-portal-fe + + # Skip TLS verification for Portal API (only for dev/self-signed certs) + # Set to false to enable NODE_TLS_REJECT_UNAUTHORIZED=0 + tlsRejectUnauthorized: true + + # Liveness probe configuration + livenessProbe: + initialDelaySeconds: 30 + periodSeconds: 10 + failureThreshold: 10 + + # Readiness probe configuration + readinessProbe: + initialDelaySeconds: 10 + periodSeconds: 5 + failureThreshold: 3 + +# ============================================================ +# Portal API Connection Configuration +# ============================================================ +portal: + # Portal API endpoint (the developer portal backend from api7 chart) + url: "https://api7-developer-portal:4321" + + # Portal token (plain text, lower priority than existingSecret) + token: "" + + # Reference to existing secret containing portal token (recommended for production) + existingSecret: "" + existingSecretKey: "portal-token" + +# ============================================================ +# Database Configuration +# ============================================================ +db: + # PostgreSQL connection string + # Format: postgres://username:password@host:port/database + url: "postgres://portal:portal123@developer-portal-fe-postgresql:5432/portal" + + # Reference to existing secret containing database URL (recommended for production) + existingSecret: "" + existingSecretKey: "db-url" + +# ============================================================ +# Authentication Configuration +# ============================================================ +auth: + # Better Auth secret key (at least 32 characters) + # Generate with: openssl rand -base64 32 + secret: "" + + # Reference to existing secret containing auth secret (recommended for production) + existingSecret: "" + existingSecretKey: "auth-secret" + +# ============================================================ +# Application URL Configuration +# ============================================================ +app: + # Base URL for the application + baseURL: "http://localhost" + + # Trusted origins for CORS + trustedOrigins: + - "http://localhost" + +# ============================================================ +# Service Configuration +# ============================================================ +service: + # Service type (ClusterIP, NodePort, LoadBalancer) + type: ClusterIP + + # Service port (exposed to the cluster) + port: 80 + + # Container port (the port the application listens on) + containerPort: 3001 + + # Service annotations + annotations: {} + +# ============================================================ +# Ingress Configuration +# ============================================================ +ingress: + # Enable ingress + enabled: false + + # Ingress class name + className: "" + + # Ingress annotations + annotations: {} + # kubernetes.io/ingress.class: nginx + # cert-manager.io/cluster-issuer: letsencrypt-prod + + # Ingress hosts configuration + hosts: + - host: developer-portal.local + paths: + - path: / + pathType: ImplementationSpecific + + # TLS configuration + tls: [] + # - secretName: developer-portal-tls + # hosts: + # - developer-portal.local + +# ============================================================ +# Built-in PostgreSQL Configuration +# ============================================================ +postgresql: + # Enable built-in PostgreSQL (set to false to use external database) + builtin: true + + # Override the full name of the PostgreSQL deployment + fullnameOverride: "developer-portal-fe-postgresql" + + # PostgreSQL image configuration + image: + registry: docker.io + repository: postgres + tag: "16" + + # Authentication settings + auth: + username: portal + password: portal123 + database: portal + + # Primary node configuration + primary: + # Persistence configuration + persistence: + enabled: true + size: 10Gi + + # Service configuration + service: + ports: + postgresql: 5432 + +# ============================================================ +# Common Configuration +# ============================================================ +# Image pull secret name +imagePullSecret: "" + +# Override the name of the chart +nameOverride: "" + +# Override the full name of the release +fullnameOverride: "" + +# Service account configuration +serviceAccount: + # Create a service account + create: true + + # Annotations for the service account + annotations: {} + + # Name of the service account (generated if not set) + name: "" + +# Pod annotations (global) +podAnnotations: {} + +# Pod security context (global) +podSecurityContext: {} + # fsGroup: 2000 + +# Container security context (global) +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +# Node selector (global) +nodeSelector: {} + +# Tolerations (global) +tolerations: [] + +# Affinity (global) +affinity: {} + +# Topology spread constraints (global) +topologySpreadConstraints: [] + +# Global resource limits and requests +resources: {} From 887a7a6488ef9bcceffb2c573c354f6d7be62649 Mon Sep 17 00:00:00 2001 From: Nic Date: Wed, 25 Mar 2026 16:03:24 +0800 Subject: [PATCH 3/5] fix: correct health probe path and PostgreSQL image config 1. Change health check path from /healthz to / (configurable via values) - The portal-fe app (v0.5.7) does not have a /healthz endpoint - /healthz returns 404, causing liveness probe failures and pod restarts - Root path / returns 200 and serves as a valid health check 2. Remove PostgreSQL image override (docker.io/postgres:16) - The bitnami/postgresql subchart expects bitnami's own image - Using the official postgres image with bitnami chart scripts is not officially supported and may break with advanced features - Let the subchart use its default bitnami/postgresql image Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- charts/developer-portal-fe/README.md | 352 +++++------------- .../templates/deployment.yaml | 4 +- charts/developer-portal-fe/values.yaml | 8 +- 3 files changed, 91 insertions(+), 273 deletions(-) diff --git a/charts/developer-portal-fe/README.md b/charts/developer-portal-fe/README.md index 1015d99..274beb2 100644 --- a/charts/developer-portal-fe/README.md +++ b/charts/developer-portal-fe/README.md @@ -1,266 +1,88 @@ -# API7 Developer Portal Frontend Helm Chart +# 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) + +A Helm chart for API7 Developer Portal Frontend + +## Maintainers + +| Name | Email | Url | +| ---- | ------ | --- | +| API7 | | | + +## Requirements + +| Repository | Name | Version | +|------------|------|---------| +| https://charts.bitnami.com/bitnami | postgresql | 12.12.10 | + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | | +| app.baseURL | string | `"http://localhost"` | | +| app.trustedOrigins[0] | string | `"http://localhost"` | | +| auth.existingSecret | string | `""` | | +| auth.existingSecretKey | string | `"auth-secret"` | | +| auth.secret | string | `""` | | +| db.existingSecret | string | `""` | | +| db.existingSecretKey | string | `"db-url"` | | +| db.url | string | `"postgres://portal:portal123@developer-portal-fe-postgresql:5432/portal"` | | +| 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.livenessProbe.failureThreshold | int | `10` | | +| developerPortal.livenessProbe.initialDelaySeconds | int | `30` | | +| developerPortal.livenessProbe.path | string | `"/"` | | +| developerPortal.livenessProbe.periodSeconds | int | `10` | | +| developerPortal.podAnnotations | object | `{}` | | +| developerPortal.podLabels | object | `{}` | | +| developerPortal.readinessProbe.failureThreshold | int | `3` | | +| developerPortal.readinessProbe.initialDelaySeconds | int | `10` | | +| developerPortal.readinessProbe.path | string | `"/"` | | +| developerPortal.readinessProbe.periodSeconds | int | `5` | | +| developerPortal.replicaCount | int | `1` | | +| developerPortal.resources | object | `{}` | | +| developerPortal.tlsRejectUnauthorized | bool | `true` | | +| developerPortal.topologySpreadConstraints | list | `[]` | | +| fullnameOverride | string | `""` | | +| imagePullSecret | string | `""` | | +| ingress.annotations | object | `{}` | | +| ingress.className | string | `""` | | +| ingress.enabled | bool | `false` | | +| ingress.hosts[0].host | string | `"developer-portal.local"` | | +| ingress.hosts[0].paths[0].path | string | `"/"` | | +| ingress.hosts[0].paths[0].pathType | string | `"ImplementationSpecific"` | | +| ingress.tls | list | `[]` | | +| nameOverride | string | `""` | | +| nodeSelector | object | `{}` | | +| podAnnotations | object | `{}` | | +| podSecurityContext | object | `{}` | | +| portal.existingSecret | string | `""` | | +| portal.existingSecretKey | string | `"portal-token"` | | +| portal.token | string | `""` | | +| portal.url | string | `"https://api7-developer-portal:4321"` | | +| postgresql.auth.database | string | `"portal"` | | +| postgresql.auth.password | string | `"portal123"` | | +| postgresql.auth.username | string | `"portal"` | | +| postgresql.builtin | bool | `true` | | +| postgresql.fullnameOverride | string | `"developer-portal-fe-postgresql"` | | +| postgresql.primary.persistence.enabled | bool | `true` | | +| postgresql.primary.persistence.size | string | `"10Gi"` | | +| postgresql.primary.service.ports.postgresql | int | `5432` | | +| resources | object | `{}` | | +| securityContext | object | `{}` | | +| service.annotations | object | `{}` | | +| service.containerPort | int | `3001` | | +| service.port | int | `80` | | +| service.type | string | `"ClusterIP"` | | +| serviceAccount.annotations | object | `{}` | | +| serviceAccount.create | bool | `true` | | +| serviceAccount.name | string | `""` | | +| tolerations | list | `[]` | | +| topologySpreadConstraints | list | `[]` | | -This Helm chart deploys the API7 Developer Portal Frontend, a Next.js application that provides a user-facing interface for the API7 Developer Portal. - -## Introduction - -The API7 Developer Portal consists of two main components: - -- **Portal API (Backend)**: Already deployed as part of the `api7` umbrella chart, listening on port 4321 -- **Developer Portal FE (Frontend)**: This chart, a Next.js application listening on port 3001 - -This chart creates an independent deployment for the Developer Portal Frontend with its own PostgreSQL database for user authentication and management. - -## Prerequisites - -- Kubernetes 1.19+ -- Helm 3.2.0+ -- PV provisioner support in the underlying infrastructure (if using built-in PostgreSQL with persistence) - -## Installing the Chart - -To install the chart with the release name `my-dev-portal`: - -```bash -helm install my-dev-portal ./charts/developer-portal-fe -``` - -The command deploys the Developer Portal Frontend on the Kubernetes cluster with the default configuration. The [Parameters](#parameters) section lists the parameters that can be configured during installation. - -## Uninstalling the Chart - -To uninstall/delete the `my-dev-portal` deployment: - -```bash -helm uninstall my-dev-portal -``` - -The command removes all the Kubernetes components associated with the chart and deletes the release. - -## Parameters - -### Application Configuration - -| Parameter | Description | Default | -|-----------|-------------|---------| -| `developerPortal.replicaCount` | Number of replicas | `1` | -| `developerPortal.image.repository` | Image repository | `api7/api7-ee-developer-portal-fe` | -| `developerPortal.image.pullPolicy` | Image pull policy | `IfNotPresent` | -| `developerPortal.image.tag` | Image tag | `v0.5.7` | -| `developerPortal.resources` | Resource limits and requests | `{}` | -| `developerPortal.extraEnvVars` | Extra environment variables | `[]` | -| `developerPortal.extraVolumes` | Extra volumes | `[]` | -| `developerPortal.extraVolumeMounts` | Extra volume mounts | `[]` | -| `developerPortal.podLabels` | Additional labels for pods | `{}` | -| `developerPortal.podAnnotations` | Additional annotations for pods | `{}` | -| `developerPortal.topologySpreadConstraints` | Topology spread constraints | `[]` | -| `developerPortal.tlsRejectUnauthorized` | Enable TLS verification (set to false for self-signed certs) | `true` | -| `developerPortal.livenessProbe.initialDelaySeconds` | Liveness probe initial delay | `30` | -| `developerPortal.livenessProbe.periodSeconds` | Liveness probe period | `10` | -| `developerPortal.livenessProbe.failureThreshold` | Liveness probe failure threshold | `10` | -| `developerPortal.readinessProbe.initialDelaySeconds` | Readiness probe initial delay | `10` | -| `developerPortal.readinessProbe.periodSeconds` | Readiness probe period | `5` | -| `developerPortal.readinessProbe.failureThreshold` | Readiness probe failure threshold | `3` | - -### Portal API Connection - -| Parameter | Description | Default | -|-----------|-------------|---------| -| `portal.url` | Portal API endpoint | `https://api7-developer-portal:4321` | -| `portal.token` | Portal token (plain text) | `""` | -| `portal.existingSecret` | Reference to existing secret containing portal token | `""` | -| `portal.existingSecretKey` | Key in existing secret for portal token | `portal-token` | - -### Database Configuration - -| Parameter | Description | Default | -|-----------|-------------|---------| -| `db.url` | PostgreSQL connection string | `postgres://portal:portal123@developer-portal-fe-postgresql:5432/portal` | -| `db.existingSecret` | Reference to existing secret containing database URL | `""` | -| `db.existingSecretKey` | Key in existing secret for database URL | `db-url` | - -### Authentication Configuration - -| Parameter | Description | Default | -|-----------|-------------|---------| -| `auth.secret` | Better Auth secret key (at least 32 characters) | `""` | -| `auth.existingSecret` | Reference to existing secret containing auth secret | `""` | -| `auth.existingSecretKey` | Key in existing secret for auth secret | `auth-secret` | - -### Application URL Configuration - -| Parameter | Description | Default | -|-----------|-------------|---------| -| `app.baseURL` | Base URL for the application | `http://localhost` | -| `app.trustedOrigins` | Trusted origins for CORS | `["http://localhost"]` | - -### Service Configuration - -| Parameter | Description | Default | -|-----------|-------------|---------| -| `service.type` | Service type | `ClusterIP` | -| `service.port` | Service port | `80` | -| `service.containerPort` | Container port | `3001` | -| `service.annotations` | Service annotations | `{}` | - -### Ingress Configuration - -| Parameter | Description | Default | -|-----------|-------------|---------| -| `ingress.enabled` | Enable ingress | `false` | -| `ingress.className` | Ingress class name | `""` | -| `ingress.annotations` | Ingress annotations | `{}` | -| `ingress.hosts` | Ingress hosts configuration | See values.yaml | -| `ingress.tls` | TLS configuration | `[]` | - -### PostgreSQL Configuration - -| Parameter | Description | Default | -|-----------|-------------|---------| -| `postgresql.builtin` | Enable built-in PostgreSQL | `true` | -| `postgresql.fullnameOverride` | Override PostgreSQL full name | `developer-portal-fe-postgresql` | -| `postgresql.image.registry` | PostgreSQL image registry | `docker.io` | -| `postgresql.image.repository` | PostgreSQL image repository | `postgres` | -| `postgresql.image.tag` | PostgreSQL image tag | `16` | -| `postgresql.auth.username` | PostgreSQL username | `portal` | -| `postgresql.auth.password` | PostgreSQL password | `portal123` | -| `postgresql.auth.database` | PostgreSQL database | `portal` | -| `postgresql.primary.persistence.size` | PostgreSQL persistence size | `10Gi` | - -### Common Configuration - -| Parameter | Description | Default | -|-----------|-------------|---------| -| `imagePullSecret` | Image pull secret name | `""` | -| `nameOverride` | Override the name of the chart | `""` | -| `fullnameOverride` | Override the full name of the release | `""` | -| `serviceAccount.create` | Create a service account | `true` | -| `serviceAccount.annotations` | Service account annotations | `{}` | -| `serviceAccount.name` | Service account name | `""` | -| `podAnnotations` | Pod annotations | `{}` | -| `podSecurityContext` | Pod security context | `{}` | -| `securityContext` | Container security context | `{}` | -| `nodeSelector` | Node selector | `{}` | -| `tolerations` | Tolerations | `[]` | -| `affinity` | Affinity | `{}` | -| `topologySpreadConstraints` | Topology spread constraints | `[]` | -| `resources` | Global resource limits and requests | `{}` | - -## Configuration Examples - -### Using External PostgreSQL - -To use an external PostgreSQL database: - -```yaml -postgresql: - builtin: false - -db: - url: "postgres://username:password@external-postgres-host:5432/portal_db" -``` - -### Using Existing Secrets - -For production deployments, it's recommended to use existing secrets: - -```yaml -portal: - existingSecret: "portal-secrets" - existingSecretKey: "portal-token" - -auth: - existingSecret: "auth-secrets" - existingSecretKey: "auth-secret" - -db: - existingSecret: "db-secrets" - existingSecretKey: "db-url" -``` - -Create the secrets manually: - -```bash -kubectl create secret generic portal-secrets --from-literal=portal-token='your-portal-token' -kubectl create secret generic auth-secrets --from-literal=auth-secret='your-auth-secret' -kubectl create secret generic db-secrets --from-literal=db-url='postgres://...' -``` - -### Enabling Ingress - -To enable ingress with TLS: - -```yaml -ingress: - enabled: true - className: "nginx" - annotations: - cert-manager.io/cluster-issuer: "letsencrypt-prod" - hosts: - - host: developer-portal.example.com - paths: - - path: / - pathType: Prefix - tls: - - secretName: developer-portal-tls - hosts: - - developer-portal.example.com -``` - -### Disabling TLS Verification (Development Only) - -For self-signed certificates in development: - -```yaml -developerPortal: - tlsRejectUnauthorized: false -``` - -**Warning**: This should never be used in production. - -## Architecture - -``` -┌──────────────────────────────────────┐ -│ api7 umbrella chart (existing) │ -│ ┌──────────────┐ │ -│ │ Portal API │ │ -│ │ (backend) │ │ -│ │ :4321 │ │ -│ └──────┬───────┘ │ -│ │ │ -│ PostgreSQL (shared api7ee) │ -└─────────┼──────────────────────────┬─┘ - │ │ - │ portal.url + token │ - ▼ │ -┌──────────────────────────────────┐ │ -│ developer-portal-fe chart │ │ -│ ┌────────────────────┐ │ │ -│ │ Developer Portal │◄─────────┘ │ -│ │ FE (Next.js) │ │ -│ │ :3001 │ │ -│ └────────┬───────────┘ │ -│ │ │ -│ PostgreSQL │ -│ (dedicated for FE users) │ -└──────────────────────────────────┘ -``` - -## Health Checks - -The application exposes a `/healthz` endpoint on port 3001 for both liveness and readiness probes. - -## Upgrading - -### To 0.2.0 - -No breaking changes. - -## License - -Copyright © 2024 API7.ai - -## Support - -For support, please contact support@api7.ai or visit https://api7.ai diff --git a/charts/developer-portal-fe/templates/deployment.yaml b/charts/developer-portal-fe/templates/deployment.yaml index ae0ec0e..aa3bcab 100644 --- a/charts/developer-portal-fe/templates/deployment.yaml +++ b/charts/developer-portal-fe/templates/deployment.yaml @@ -57,14 +57,14 @@ spec: protocol: TCP livenessProbe: httpGet: - path: /healthz + path: {{ .Values.developerPortal.livenessProbe.path | default "/" }} port: http initialDelaySeconds: {{ .Values.developerPortal.livenessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.developerPortal.livenessProbe.periodSeconds }} failureThreshold: {{ .Values.developerPortal.livenessProbe.failureThreshold }} readinessProbe: httpGet: - path: /healthz + path: {{ .Values.developerPortal.readinessProbe.path | default "/" }} port: http initialDelaySeconds: {{ .Values.developerPortal.readinessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.developerPortal.readinessProbe.periodSeconds }} diff --git a/charts/developer-portal-fe/values.yaml b/charts/developer-portal-fe/values.yaml index 471e135..39e6baa 100644 --- a/charts/developer-portal-fe/values.yaml +++ b/charts/developer-portal-fe/values.yaml @@ -56,12 +56,14 @@ developerPortal: # Liveness probe configuration livenessProbe: + path: / initialDelaySeconds: 30 periodSeconds: 10 failureThreshold: 10 # Readiness probe configuration readinessProbe: + path: / initialDelaySeconds: 10 periodSeconds: 5 failureThreshold: 3 @@ -169,12 +171,6 @@ postgresql: # Override the full name of the PostgreSQL deployment fullnameOverride: "developer-portal-fe-postgresql" - # PostgreSQL image configuration - image: - registry: docker.io - repository: postgres - tag: "16" - # Authentication settings auth: username: portal From 6d0b6c65f8fcddacb2f7e0946a982c4a15dfd5f2 Mon Sep 17 00:00:00 2001 From: Nic Date: Wed, 25 Mar 2026 16:14:40 +0800 Subject: [PATCH 4/5] fix: independent existingSecret support for portal, db, and auth Previously, setting portal.existingSecret would skip creating the entire chart-managed secret, breaking DB_URL and AUTH_SECRET env vars unless they also had their own existingSecret configured. Now each secret field (portal.existingSecret, db.existingSecret, auth.existingSecret) is independently evaluated: - The chart secret is created if ANY of the three need it - Only keys without an existingSecret override are included - Each env var in the deployment references its own existingSecret or falls back to the chart-managed secret independently Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- charts/developer-portal-fe/templates/_helpers.tpl | 6 +----- charts/developer-portal-fe/templates/deployment.yaml | 2 +- charts/developer-portal-fe/templates/secret.yaml | 8 +++++++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/charts/developer-portal-fe/templates/_helpers.tpl b/charts/developer-portal-fe/templates/_helpers.tpl index 7ba142a..4af258d 100644 --- a/charts/developer-portal-fe/templates/_helpers.tpl +++ b/charts/developer-portal-fe/templates/_helpers.tpl @@ -75,12 +75,8 @@ Usage: {{- end -}} {{/* -Get the secret name for portal token, auth secret, and db url +Get the chart-managed secret name */}} {{- define "developer-portal-fe.secretName" -}} -{{- if .Values.portal.existingSecret }} -{{- .Values.portal.existingSecret }} -{{- else }} {{- include "developer-portal-fe.fullname" . }}-secrets {{- end }} -{{- end }} diff --git a/charts/developer-portal-fe/templates/deployment.yaml b/charts/developer-portal-fe/templates/deployment.yaml index aa3bcab..cb3af01 100644 --- a/charts/developer-portal-fe/templates/deployment.yaml +++ b/charts/developer-portal-fe/templates/deployment.yaml @@ -87,7 +87,7 @@ spec: - name: PORTAL_TOKEN valueFrom: secretKeyRef: - name: {{ include "developer-portal-fe.secretName" . }} + name: {{ if .Values.portal.existingSecret }}{{ .Values.portal.existingSecret }}{{ else }}{{ include "developer-portal-fe.secretName" . }}{{ end }} key: {{ .Values.portal.existingSecretKey }} - name: DB_URL valueFrom: diff --git a/charts/developer-portal-fe/templates/secret.yaml b/charts/developer-portal-fe/templates/secret.yaml index ad67304..745ef81 100644 --- a/charts/developer-portal-fe/templates/secret.yaml +++ b/charts/developer-portal-fe/templates/secret.yaml @@ -1,4 +1,4 @@ -{{- if not .Values.portal.existingSecret }} +{{- if or (not .Values.portal.existingSecret) (not .Values.db.existingSecret) (not .Values.auth.existingSecret) }} apiVersion: v1 kind: Secret metadata: @@ -8,19 +8,25 @@ metadata: {{- include "developer-portal-fe.labels" . | nindent 4 }} type: Opaque data: + {{- if not .Values.portal.existingSecret }} {{- if .Values.portal.token }} portal-token: {{ .Values.portal.token | b64enc | quote }} {{- else }} portal-token: "" {{- end }} + {{- end }} + {{- if not .Values.auth.existingSecret }} {{- if .Values.auth.secret }} auth-secret: {{ .Values.auth.secret | b64enc | quote }} {{- else }} auth-secret: "" {{- end }} + {{- end }} + {{- if not .Values.db.existingSecret }} {{- if .Values.db.url }} db-url: {{ .Values.db.url | b64enc | quote }} {{- else }} db-url: "" {{- end }} + {{- end }} {{- end }} From af0b76fd5cbce28156ca34826f3af372ea908fbd Mon Sep 17 00:00:00 2001 From: Nic Date: Wed, 25 Mar 2026 16:20:12 +0800 Subject: [PATCH 5/5] fix: use api7/postgresql image to match api7 umbrella chart convention Override bitnami's default postgresql image with api7/postgresql:15.4.0-debian-11-r45, matching the same image used by the api7 umbrella chart. This ensures consistency and avoids pulling bitnami's default image which may be unavailable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- charts/developer-portal-fe/README.md | 3 +++ charts/developer-portal-fe/values.yaml | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/charts/developer-portal-fe/README.md b/charts/developer-portal-fe/README.md index 274beb2..7789107 100644 --- a/charts/developer-portal-fe/README.md +++ b/charts/developer-portal-fe/README.md @@ -71,6 +71,9 @@ A Helm chart for API7 Developer Portal Frontend | postgresql.auth.username | string | `"portal"` | | | postgresql.builtin | bool | `true` | | | postgresql.fullnameOverride | string | `"developer-portal-fe-postgresql"` | | +| postgresql.image.registry | string | `"docker.io"` | | +| postgresql.image.repository | string | `"api7/postgresql"` | | +| postgresql.image.tag | string | `"15.4.0-debian-11-r45"` | | | postgresql.primary.persistence.enabled | bool | `true` | | | postgresql.primary.persistence.size | string | `"10Gi"` | | | postgresql.primary.service.ports.postgresql | int | `5432` | | diff --git a/charts/developer-portal-fe/values.yaml b/charts/developer-portal-fe/values.yaml index 39e6baa..e32744c 100644 --- a/charts/developer-portal-fe/values.yaml +++ b/charts/developer-portal-fe/values.yaml @@ -177,6 +177,12 @@ postgresql: password: portal123 database: portal + # PostgreSQL image configuration (use api7's image for compatibility) + image: + registry: docker.io + repository: api7/postgresql + tag: 15.4.0-debian-11-r45 + # Primary node configuration primary: # Persistence configuration