From 026bcb2b3137416851f09133cec897d7246ca24b Mon Sep 17 00:00:00 2001 From: Danijel Simeunovic Date: Thu, 23 Apr 2026 18:45:57 +0000 Subject: [PATCH] feature/backstage (#13) Reviewed-on: https://git.forteapps.net/Forte/launchpad/pulls/13 Reviewed-by: gitea_admin Co-authored-by: Danijel Simeunovic Co-committed-by: Danijel Simeunovic --- .gitea/workflows/ai-review.yaml | 1 + .../backstage-keycloak-client-config.yaml | 43 ++++++ docs/REFERENCE.md | 77 +++++++++++ infra/base/backstage.yaml | 43 ++++++ infra/base/kustomization.yaml | 1 + infra/values/base/backstage-values.yaml | 128 ++++++++++++++++++ infra/values/upc-dev/backstage-values.yaml | 12 ++ 7 files changed, 305 insertions(+) create mode 100644 cluster-resources/backstage-keycloak-client-config.yaml create mode 100644 infra/base/backstage.yaml create mode 100644 infra/values/base/backstage-values.yaml create mode 100644 infra/values/upc-dev/backstage-values.yaml diff --git a/.gitea/workflows/ai-review.yaml b/.gitea/workflows/ai-review.yaml index 808bb33..178a0ad 100644 --- a/.gitea/workflows/ai-review.yaml +++ b/.gitea/workflows/ai-review.yaml @@ -34,6 +34,7 @@ jobs: with: submodules: true fetch-depth: 0 + token: ${{ secrets.AI_REVIEW_TOKEN }} - name: Run inline review uses: docker://nikitafilonov/ai-review:v0.64.0 diff --git a/cluster-resources/backstage-keycloak-client-config.yaml b/cluster-resources/backstage-keycloak-client-config.yaml new file mode 100644 index 0000000..7bfd34f --- /dev/null +++ b/cluster-resources/backstage-keycloak-client-config.yaml @@ -0,0 +1,43 @@ +# Self-service Keycloak client config for Backstage. +# Kyverno clones this to the keycloak namespace, where the +# keycloak-client-registrar CronJob processes it and creates +# the backstage-oidc-credentials Secret in the backstage namespace. +apiVersion: v1 +kind: Secret +metadata: + name: keycloak-client-backstage + namespace: backstage + labels: + keycloak.forteapps.net/client-config: "true" +stringData: + client.json: | + { + "clientId": "backstage", + "name": "Backstage Developer Portal", + "redirectUris": ["https://backstage.forteapps.net/api/auth/oidc/handler/frame"], + "webOrigins": ["https://backstage.forteapps.net"], + "defaultClientScopes": ["openid", "email", "profile"], + "protocolMappers": [ + { + "name": "email_verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-hardcoded-claim-mapper", + "config": { + "claim.name": "email_verified", + "claim.value": "true", + "jsonType.label": "boolean", + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + } + ], + "secret": { + "namespace": "backstage", + "name": "backstage-oidc-credentials", + "keys": { + "clientId": "AUTH_OIDC_CLIENT_ID", + "clientSecret": "AUTH_OIDC_CLIENT_SECRET" + } + } + } diff --git a/docs/REFERENCE.md b/docs/REFERENCE.md index d026c47..05e4c89 100644 --- a/docs/REFERENCE.md +++ b/docs/REFERENCE.md @@ -965,6 +965,83 @@ ignore: - Check Gitea Actions tab for workflow run status and logs - Monitor Anthropic usage dashboard for token consumption +### Backstage / RHDH (Developer Portal) + +**Chart**: `backstage` (RHDH — Red Hat Developer Hub) +**Version**: `5.8.0` +**Namespace**: `backstage` +**Helm Repo**: `https://redhat-developer.github.io/rhdh-chart` +**Image**: `quay.io/rhdh-community/rhdh:next` + +**Purpose**: Internal developer portal where teams register and broadcast themselves, their applications, APIs, and systems. Provides a unified catalog, templates, and documentation hub. + +**Why RHDH over vanilla Backstage**: Ships 27+ plugins pre-bundled (ArgoCD, Kubernetes, Keycloak, GitHub, GitLab, Jira, SonarQube, Tekton, Jenkins, Quay, and more). Supports dynamic plugin installation at runtime — no image rebuilds needed. + +**Configuration** (`infra/values/base/backstage-values.yaml`): +- OpenShift Route disabled (`route.enabled: false`) — uses Traefik ingress instead +- PostgreSQL subchart enabled for persistence (2Gi) +- SecurityContext configured for vanilla Kubernetes (non-OpenShift) +- Traefik ingress with `websecure` entrypoint +- App title: "Forte Developer Portal" +- Dynamic plugins: loads `dynamic-plugins.default.yaml` (all 27+ bundled plugins) +- Catalog rules: Component, System, API, Resource, Location, Template, Group, User, Domain + +**Authentication** (Keycloak OIDC): +- Uses the self-service registrar pattern (see [Keycloak Client Registrar](#keycloak-client-registrar)) +- Config Secret: `cluster-resources/backstage-keycloak-client-config.yaml` +- Kyverno clones it → registrar creates `backstage-oidc-credentials` Secret in `backstage` namespace +- Credential keys: `AUTH_OIDC_CLIENT_ID`, `AUTH_OIDC_CLIENT_SECRET` (loaded via `extraEnvVarsSecrets`) +- Redirect URI: `https://backstage.forteapps.net/api/auth/oidc/handler/frame` +- Sign-in resolver: `emailMatchingUserEntityProfileEmail` + +**Catalog Discovery** (Gitea): +- Auto-discovers `catalog-info.yaml` from all repos in the `Forte` organization +- Scans every 30 minutes via the Gitea catalog provider plugin +- Gitea SCM integration configured for URL resolution (`git.forteapps.net`) + +**Catalog Registration**: +Teams register services by adding a `catalog-info.yaml` to their repo root: +```yaml +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: my-service + description: My service description + annotations: + backstage.io/source-location: url:https://git.forteapps.net/Forte/my-service +spec: + type: service + lifecycle: production + owner: team-name +``` + +Repos with this file are auto-discovered — no manual registration needed. + +**Dynamic Plugins**: +Add plugins at runtime via `global.dynamic.plugins` in values — no image rebuild: +```yaml +global: + dynamic: + plugins: + - package: "@scope/my-plugin@1.0.0" + integrity: "sha512-..." +``` + +**Per-cluster Configuration** (`infra/values/upc-dev/backstage-values.yaml`): +```yaml +global: + host: backstage.forteapps.net +upstream: + backstage: + appConfig: + app: + baseUrl: https://backstage.forteapps.net + backend: + baseUrl: https://backstage.forteapps.net + ingress: + host: backstage.forteapps.net +``` + ### Keycloak Client Registrar **Type**: CronJob (deployed via Keycloak Helm chart `extraDeploy`) diff --git a/infra/base/backstage.yaml b/infra/base/backstage.yaml new file mode 100644 index 0000000..5130547 --- /dev/null +++ b/infra/base/backstage.yaml @@ -0,0 +1,43 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: backstage + namespace: argocd + annotations: + argocd.argoproj.io/sync-wave: "1" + labels: + app.kubernetes.io/name: backstage + app.kubernetes.io/part-of: developer-portal + app.kubernetes.io/managed-by: argocd + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: default + + sources: + - repoURL: https://redhat-developer.github.io/rhdh-chart + chart: backstage + targetRevision: "5.8.0" + helm: + releaseName: backstage + valueFiles: + - $values/infra/values/base/backstage-values.yaml + - $values/infra/values/upc-dev/backstage-values.yaml + + - repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git + targetRevision: HEAD + ref: values + + destination: + server: https://kubernetes.default.svc + namespace: backstage + + syncPolicy: + automated: + prune: true + selfHeal: true + allowEmpty: false + syncOptions: + - CreateNamespace=true + - Validate=true + - ServerSideApply=true diff --git a/infra/base/kustomization.yaml b/infra/base/kustomization.yaml index 5c1beb1..f6206a4 100644 --- a/infra/base/kustomization.yaml +++ b/infra/base/kustomization.yaml @@ -22,3 +22,4 @@ resources: - tempo.yaml - grafana-dashboards.yaml - network-policies-application.yaml +- backstage.yaml diff --git a/infra/values/base/backstage-values.yaml b/infra/values/base/backstage-values.yaml new file mode 100644 index 0000000..652278d --- /dev/null +++ b/infra/values/base/backstage-values.yaml @@ -0,0 +1,128 @@ +# Red Hat Developer Hub (RHDH) - Internal Developer Portal +# Helm chart: https://github.com/redhat-developer/rhdh-chart +# Includes 27+ plugins out of the box: ArgoCD, Kubernetes, Keycloak, +# GitHub, GitLab, Jira, SonarQube, Tekton, Jenkins, and more. + +global: + auth: + backend: + enabled: true + dynamic: + includes: + - dynamic-plugins.default.yaml + plugins: [] + +# Disable OpenShift Route (not on OpenShift) +route: + enabled: false + +upstream: + backstage: + image: + registry: quay.io + repository: rhdh-community/rhdh + tag: next + + podSecurityContext: + runAsUser: 1001 + runAsGroup: 1001 + fsGroup: 1001 + + resources: + requests: + cpu: 250m + memory: 1Gi + limits: + cpu: 1000m + memory: 2560Mi + + extraEnvVarsSecrets: + - backstage-oidc-credentials + + appConfig: + app: + title: "Forte Backstage" + baseUrl: http://localhost:7007 + + backend: + baseUrl: http://localhost:7007 + + # -- Keycloak OIDC authentication + signInPage: oidc + auth: + environment: production + providers: + oidc: + production: + metadataUrl: https://id.forteapps.net/realms/forte/.well-known/openid-configuration + clientId: ${AUTH_OIDC_CLIENT_ID} + clientSecret: ${AUTH_OIDC_CLIENT_SECRET} + prompt: auto + signIn: + resolvers: + - resolver: emailMatchingUserEntityProfileEmail + + # -- Gitea SCM integration (for catalog URL resolution) + integrations: + gitea: + - host: git.forteapps.net + + # -- Software catalog + catalog: + rules: + - allow: + - Component + - System + - API + - Resource + - Location + - Template + - Group + - User + - Domain + providers: + # Auto-discover catalog-info.yaml from all Forte org repos + gitea: + forte: + organization: Forte + host: git.forteapps.net + catalogPath: catalog-info.yaml + schedule: + frequency: { minutes: 30 } + timeout: { minutes: 3 } + locations: + # Backstage's own org data (bootstrap teams, systems, domains) + # - type: url + # target: https://git.forteapps.net/Forte/backstage-catalog/raw/branch/main/org.yaml + # rules: + # - allow: [Group, User, System, Domain] + + ingress: + enabled: true + className: traefik + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + + postgresql: + enabled: true + image: + registry: docker.io + repository: library/postgres + tag: "15" + primary: + persistence: + enabled: true + size: 2Gi + podSecurityContext: + enabled: true + fsGroup: 26 + runAsUser: 26 + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + cpu: 250m + memory: 512Mi + volumePermissions: + enabled: true diff --git a/infra/values/upc-dev/backstage-values.yaml b/infra/values/upc-dev/backstage-values.yaml new file mode 100644 index 0000000..c0ffd25 --- /dev/null +++ b/infra/values/upc-dev/backstage-values.yaml @@ -0,0 +1,12 @@ +global: + host: backstage.forteapps.net + +upstream: + backstage: + appConfig: + app: + baseUrl: https://backstage.forteapps.net + backend: + baseUrl: https://backstage.forteapps.net + ingress: + host: backstage.forteapps.net