W'xOps Portal v0.2.0 — Golden-Path Scaffolding & Developer Experience
The first release where the portal can create and manage a full service lifecycle
end-to-end: a developer fills in a wizard, the portal commits XTenantApp +
XTenantDatabase + ExternalSecrets + Kustomize overlays + catalog entities to
gitops-infra, ArgoCD and Crossplane provision everything, and the entity detail
page shows live CI status, releases, packages, and activity.
What's New
Golden-path scaffolding
Project creation wizard
Four-step wizard — template selection → runtime config → platform features → review and create. Each step is independently navigable; the final step shows a per-step progress screen with timing as the portal commits files and creates resources.
Template system
Templates live in gitops-infra under a configurable directory. Each template
carries a template.yaml that declares metadata, defaults, and recommends
so the wizard can pre-populate fields and suggest optional features. Runtimes
(language, version, package manager) are selectable per template.
Manifest generation
The portal generates and commits:
tenants-apps/{team}/{app}/base/—xtenant-app.yaml,xtenant-database.yaml(when database enabled),external-secret-*.yaml, catalog entity filestenants-apps/{team}/{app}/overlays/dev/—kustomization.yaml,image-transformer.yaml,patch-xtenant-app.yamltenants/{team}/{app}.yaml— ArgoCD Image Updater CR
ArgoCD Image Updater integration
image-transformer.yaml is required for Kustomize to reach spec/parameters/image
on the XTenantApp CRD (Kustomize only natively substitutes images in standard
Deployment/StatefulSet specs). The generated kustomization.yaml intentionally
has no images: block — Image Updater adds it on first build. A static block
would be overwritten on every reconcile, resetting the tag to latest.
The Image Updater CR is committed to tenants/{team}/{appName}.yaml
(not inside tenants-apps/) with metadata.name: {appName} for a clean
ArgoCD control-plane view.
Overlay patch correctness
patch-xtenant-app.yaml contains only env-specific overrides (replicas,
resources, ingress, secretsFrom). appName, namespace, and image use
omitempty so they are absent from the patch and inherit from base — previously
empty string values were overriding the base and breaking the deployed config.
Git-flow branching and branch protection
Scaffolded repos have three branches: develop (default, direct push allowed),
staging and main (PR required; main has a CI bot whitelist). CI bot identity
(GITEA_BOT_USERNAME / GITEA_BOT_EMAIL) is injected into scaffolded CI files.
Vault and ExternalSecret
The portal writes a Vault KV secret at secret/{team}/{app} (create/update only —
never reads or deletes) and generates ExternalSecret manifests that reference it.
Existence of the Gitea repo and gitops config is checked before any Vault write.
Import existing repos
Register an existing Gitea project into the catalog without re-scaffolding. The
import wizard lists all org repos, auto-fills the entity YAML, and commits catalog
entity files to gitops-infra.
Config management via PR
Edit-config wizard
Update XTenantApp platform features (replicas, resources, ingress, secrets, probes, rollout strategy) through a three-step diff-review UI:
- Edit form with YAML preview
- Review — structured summary of changes
- Success — "what was updated" checklist and PR status
The form matches the scaffold wizard's visual language: rounded-xl border cards,
lg:grid-cols-2 equal columns, pill step badges, nav buttons outside the grid.
templateId preserved on update
Updating a project's config previously cleared templateId from the XTenantApp
manifest — the backend rebuilt the manifest from the request struct without that
field. templateId is now echoed back from the frontend and threaded through the
backend rebuild.
Gitops PR URL security
gitops-infra PR URLs are not included in any API response. Activity feed and
success panels show titles and status only.
CI/CD and release visibility
Each catalog Component entity (with a gitea/source-location annotation) now
shows three live-data cards pulled from the Gitea API:
CI/CD status card
Latest Gitea Actions workflow runs with status badges (passed / failed / running), workflow name, branch, and relative time. Auto-refreshes every 30 seconds.
Releases card
Git releases and container images from the Gitea Package Registry. Shows version timeline, pre-release badge, release notes preview, image tags with pull command.
Packages card
Dependency tracking from the repo's manifest files: go.mod, package.json,
requirements.txt, pyproject.toml. Shows direct vs. dev dependencies per
ecosystem. Single fetch on mount (deps don't change often enough to poll).
Activity feed
Two-tier loading: five most-recent portal-managed PRs load immediately; "Show all"
expands to a full paginated view. Filtered by the portal-managed Gitea label so
only portal-created PRs appear. Role-scoped: tenant teams see only their own team's
activity.
Lifecycle webhook
The lifecycle sync CI workflow lives in gitops-infra (not in the scaffolded
project). It fires on push to main when
tenants-apps/**/overlays/dev/kustomization.yaml changes — Image Updater's
write-back after a successful build triggers the webhook, which promotes the
entity lifecycle to development.
Team managers ({orgName}:{teamName}:Managers) can promote lifecycle stages
alongside platform-team. PORTAL_EXTERNAL_URL has been removed; CI uses
PORTAL_URL from Gitea repository secrets on gitops-infra.
Cluster views
Namespace listing from Pinniped groups
GET /api/v1/clusters/:id/namespaces previously called the K8s namespaces
endpoint, which returns everything (cluster-admin) or 403 (everyone else). Namespaces
are now derived from the user's Pinniped group membership: Gitea OIDC groups arrive
as orgName:teamName, mapping to tenant-{orgName}. Platform-team users fall
through to the K8s API.
Identity card reload button
Reload re-fetches the Pinniped WhoAmI, refreshes namespace listing, and triggers a full refresh of pods and deployments — no browser reload required.
Upgrade Notes
New environment variables
| Variable | Required | Default | Notes |
|---|---|---|---|
GITEA_TEMPLATE_OWNER | No | (GITEA_CATALOG_OWNER) | Org owning the scaffold template repo |
GITEA_TEMPLATE_REPO | No | scaffold-templates | Repo containing scaffold templates |
GITEA_BOT_USERNAME | No | — | CI bot username — added to main branch push whitelist |
GITEA_BOT_EMAIL | No | — | CI bot email — substituted into scaffold template CI files |
VAULT_ADDR | No | — | Vault server URL. When empty, Vault write is skipped silently. |
VAULT_TOKEN | No | — | Short-lived token with wxops-portal policy. |
VAULT_KV_MOUNT | No | secret | KV v2 mount. Secrets written to {mount}/{team}/{appName}. |
WEBHOOK_TOKEN | No | — | Shared secret for lifecycle webhook. |
DEV_BYPASS_AUTH | No | false | Skip OIDC — auto-login as dev / platform-team. Local dev only. |
PORTAL_EXTERNAL_URL removed
Rename the secret used by the Gitea CI runner: PORTAL_EXTERNAL_URL → PORTAL_URL
(Gitea repository secret on gitops-infra). The webhook token secret name is unchanged.
Namespace RBAC
Ensure ClusterRoleBindings on spoke clusters use the full orgName:teamName string
as the subject (not plain teamName). Tenant namespaces must be named tenant-{orgName}.
ArgoCD Image Updater
Remove any static images: block from existing overlay kustomization.yaml files
before enabling Image Updater — the two will fight over the tag value on every reconcile.
