Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Editor / OS
.DS_Store
.idea/
.vscode/
*.swp
*~

# Helm artifacts
charts/*.tgz
charts/**/charts/

# Secrets — never commit, per global policy
*.secret.yaml
*.secret.yml
*.pem
*.key
*_rsa
*.crt
*.p12
secrets/
env.local
.env
.env.*

# Discovery output (live cluster snapshots — see openspec task 1.2)
openspec/**/cluster-state.yaml

# Local tool caches
.kubeconform-cache/
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Changelog

All notable changes to this repository are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Added

- **2026-05-01** — Initial openspec change `bootstrap-react-platform`. Proposal/design/tasks for mirroring the Nextcloud-base GitOps shape onto the WOO PWA: ApplicationSet + layered Helm values (`common.yaml` → `env/*.yaml` → `tenants/tenant-*.yaml`), vendored `woo-website` chart, AppProject sync-window governance. Replaces the `mcc create-react` per-tenant manifest generator. Tenant files become 2 lines (`name`, `environment`) for the common case; namespace pairs with the Nextcloud co-tenant (`<org>-<env>`); image tag pinned to semver in `common.yaml` (no more `latest`); branding migration sources truth from live cluster state. No code or values scaffolded yet — planning docs only. Files: `openspec/changes/bootstrap-react-platform/{.openspec.yaml,proposal.md,design.md,tasks.md}`.
- **2026-05-01** — Document external dependencies in `bootstrap-react-platform`: DNS handled by `cluster-infra/external-dns` (Cloudflare, `policy: sync`, zones `commonground.nu`/`openwoo.app`/`opencatalogi.nl`) — tenant adds/removes auto-create/auto-reap DNS records. TLS via cert-manager + HTTP-01. Recorded in `design.md` (new "External dependencies" subsection), `proposal.md` (Removed from Scope), and `tasks.md` (task 7.1 will surface this in `docs/ADDING-TENANT.md`).
- **2026-05-01** — Scaffold `react-platform/` (openspec phases 2–7). Vendored `charts/woo-website/` from `woo-website-template-apiv2@b1ac4e89` (refactor branch) plus added `templates/networkpolicy.yaml` for pod-label-scoped default-deny + allow-ingress + allow-egress policies. Wrote `react-platform/values/{common,env/accept,env/prod}.yaml` and `templates/tenant-template.yaml` (image pinned to `1.0.0`, `nodeSelector: role: prod-nextcloud`, ingress className `nginx`, security context platform-grade). Wrote AppProject `react-platform` and ApplicationSet `react-tenants` (mirrors Nextcloud-base shape, namespace `<name>-<env>`, Application name `<name>-<env>-reactfront`). Wrote `scripts/{validate-values,smoke-checks}.sh` (smoke-check reproduces ApplicationSet inline values in bash for offline render testing). Wrote `docs/{ADDING-TENANT,ROLLOUTS,MIGRATION}.md`, `CLAUDE.md`, and updated `README.md`. Synthetic tenant `tenant-test-mcc.yaml` added as canary placeholder; smoke-checks pass (9 resources clean). Phase 1 (cluster discovery) and 8–10 (cut-overs) are deferred to live operations.
134 changes: 134 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

This is a **GitOps platform** for deploying multiple isolated WOO PWA frontends (React/Gatsby) on Kubernetes using Argo CD. It is the **frontend equivalent of `Nextcloud-base`**: same multi-tenant ApplicationSet shape, same wave-based rollouts, same AppProject sync-window governance — minus everything a static frontend doesn't need (no DB, no Redis, no PgBouncer, no External Secrets, no persistent storage).

Each WOO PWA tenant lives in the **same namespace as its Nextcloud co-tenant** (e.g. `almere-accept`, `almere-prod`). The frontend pod and the Nextcloud backend share a namespace per (org, env) tuple.

## Common Commands

### Local validation (before commit)
```bash
./react-platform/scripts/validate-values.sh # required-field check on tenant files
./react-platform/scripts/smoke-checks.sh # helm template + kubeconform on every tenant
```

### Required local tools
`helm`, `kubeconform`, `yq` (mikefarah)

## Architecture

### Layered Helm values (3-file merge per tenant)
ApplicationSet `react-tenants` composes values in this order:
1. `react-platform/values/common.yaml` — base config for every tenant on every cluster (image, security, nodepool, ingress class)
2. `react-platform/values/env/{accept,prod}.yaml` — environment-specific overrides (replicas, HPA, resources)
3. `react-platform/values/tenants/tenant-<name>.yaml` — per-tenant: name, environment, optional branding

Plus an inline `values:` block computed by the ApplicationSet template that derives:
- `global.domain` — `<name>.openwoo.app` (prod) or `<name>.accept.openwoo.app` (accept)
- `pwa.upstream.host` and `pwa.upstream.base` — `<name>.commonground.nu` etc.
- TLS secret name: `<name>-<env>-tls`
- `commonLabels` including `app.kubernetes.io/part-of: react-platform` for NetworkPolicy targeting
- All branding env vars from `tenant.branding`

### Tenant definition pattern
Two lines is the common case:
```yaml
tenant:
name: test-mcc
environment: accept
```

With branding (Almere-style):
```yaml
tenant:
name: almere
environment: accept
branding:
organisationName: "Gemeente Almere"
themeClassname: almere-theme
footerHideLogo: true
jumbotronImageUrl: "https://..."
faviconUrl: "..."
```

### Key directories
- `charts/woo-website/` — vendored Helm chart from `woo-website-template-apiv2`. See `charts/woo-website/UPSTREAM` for the sync point and react-base divergences.
- `react-platform/argo/` — AppProject (`react-platform`) and ApplicationSet (`react-tenants`)
- `react-platform/values/` — common, env, tenants, templates
- `react-platform/scripts/` — `validate-values.sh`, `smoke-checks.sh`
- `react-platform/docs/` — `ADDING-TENANT.md`, `ROLLOUTS.md`, `MIGRATION.md`
- `openspec/changes/` — design proposals (see `bootstrap-react-platform/` for the bootstrap)

### Namespace convention
Namespace = `<tenant.name>-<tenant.environment>`. Paired with the Nextcloud co-tenant managed by `Nextcloud-base`. The namespace is **not** auto-created by this repo's ApplicationSet — it must already exist via Nextcloud-base before the first sync of a frontend tenant.

### Co-tenancy with Nextcloud
The frontend pod and Nextcloud pod share a namespace per (org, env). Two consequences:
- **NetworkPolicies are pod-label scoped** (`app.kubernetes.io/part-of: react-platform`) — they don't relabel the namespace and don't interfere with Nextcloud-base's namespace-label-scoped policies.
- **Namespace label stays `nextcloud-platform`**. Don't change it; this repo's NetworkPolicies don't depend on it.

### Sync windows (governance)
The AppProject blocks platform-level syncs during weekday office hours (07:00–17:00 Europe/Amsterdam). Operational rule is stricter:

- **Platform changes** (`values/common.yaml`, `values/env/*.yaml`, `argo/`, `charts/`): Mon–Thu 17:00–07:00 only. Never on Fri evenings, Saturdays, or Sundays — unless mwest2020 explicitly approves. Image-tag bumps count as platform changes.
- **Tenant config additions** (`values/tenants/` only): allowed at any time.
- **Canary (wave 0)**: syncs first in every rollout; validate before allowing other waves to proceed.

### External dependencies (not owned by this repo)
- **DNS**: `cluster-infra/external-dns` runs cluster-wide with the Cloudflare provider, `policy: sync`, watches `Ingress` resources in `commonground.nu`/`openwoo.app`/`opencatalogi.nl` zones. Tenant Ingress creation/deletion auto-creates and auto-reaps the DNS record. **Operators never touch Cloudflare to add or remove a tenant.**
- **TLS**: cert-manager via HTTP-01. Cloudflare proxy is intentionally off in `cluster-infra` so HTTP-01 challenges work. Don't enable proxy.
- **Ingress controller**: ingress-nginx in namespace `ingress-nginx` (referenced by the chart's `networkPolicy.ingressNamespace` value).

## CI/CD pipeline

GitHub Actions on every push/PR (`.github/workflows/validate.yaml` — TODO, task 6.3 in openspec):

| Job | Blocking? | What it checks |
|---|---|---|
| YAML lint | Yes | yamllint, sane line lengths |
| Values validation | Yes | `validate-values.sh` |
| Helm + kubeconform | Yes | `smoke-checks.sh` |
| Secret scanning | Yes | gitleaks |

## Vendored chart

`charts/woo-website/` is a pinned copy of `woo-website-template-apiv2/helm/woo-website` at a specific upstream commit (see `charts/woo-website/UPSTREAM`). Divergences from upstream:

- `templates/networkpolicy.yaml` — added by react-base (pod-label-scoped NetworkPolicies)
- `values.yaml` — added `networkPolicy.{enabled,ingressNamespace}` block

Bumping the vendored chart is a platform-level change (sync window applies).

## Adding a new tenant

See `react-platform/docs/ADDING-TENANT.md`. Short version: copy template, edit name + environment, commit, push. ApplicationSet picks it up on next sync. DNS, TLS, NetworkPolicies, and namespace co-tenancy are all automatic.

## Migration from old standalone Applications

See `react-platform/docs/MIGRATION.md`. Per-tenant cut-over with 1–3 minutes of downtime, inside the sync window. Canary (`test-mcc-accept`) first, soak 24h, then waves.

## Resource policy

Static nginx — tiny baseline. `common.yaml` ships `cpu: 50m/500m`, `memory: 64Mi/256Mi`. Prod env bumps the limit ceiling slightly. No need for per-tenant resource overrides unless a specific tenant has unusual traffic.

## What this repo deliberately does NOT have

- No database (frontend serves only public content)
- No Redis or PgBouncer (no shared cache, no DB connections)
- No External Secrets Operator (no tenant secrets)
- No PVCs or StorageClasses (image is self-contained)
- No `create-tenant-secret.sh` script (nothing to create)
- No S3 storage layer (no user data)

If any of those become necessary later, prefer adding them via a new openspec change rather than expanding this scaffold organically.

## Operational gotchas

- **`pwa.image.tag: latest` is never used.** Tag is pinned in `common.yaml`. Bumping = platform change = sync window applies.
- **Namespace must exist before first sync.** Frontend ApplicationSet does not create namespaces — Nextcloud-base does. If a frontend tenant exists without a Nextcloud co-tenant, create the namespace manually with `app.kubernetes.io/part-of: nextcloud-platform` label.
- **Don't enable Cloudflare proxy** for any zone external-dns manages — it breaks cert-manager HTTP-01.
- **NetworkPolicies select pods, not namespaces.** Don't relabel the namespace expecting policy changes to follow; they won't.
187 changes: 187 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,188 @@
# React-base

<div align="center">

![React](https://img.shields.io/badge/React-61DAFB?style=for-the-badge&logo=react&logoColor=black)
![Gatsby](https://img.shields.io/badge/Gatsby-663399?style=for-the-badge&logo=gatsby&logoColor=white)
![Kubernetes](https://img.shields.io/badge/Kubernetes-326CE5?style=for-the-badge&logo=kubernetes&logoColor=white)
![ArgoCD](https://img.shields.io/badge/Argo%20CD-EF7B4D?style=for-the-badge&logo=argo&logoColor=white)
![Helm](https://img.shields.io/badge/Helm-0F1689?style=for-the-badge&logo=helm&logoColor=white)

**Een GitOps-platform voor het uitrollen van meerdere WOO PWA-frontends op Kubernetes**

[Quick Start](#-quick-start) •
[Architectuur](#-architectuur) •
[Tenant toevoegen](#-tenant-toevoegen) •
[Documentatie](#-documentatie)

</div>

---

## ✨ Kenmerken

- 🪶 **Tenant in 2 regels YAML** — alles afgeleid van naam + omgeving
- 🔄 **GitOps-first** — alle config in Git, automatische sync via Argo CD
- 🤝 **Co-tenancy met Nextcloud** — frontend deelt namespace met zijn Nextcloud-backend
- 🌐 **Automatische DNS + TLS** — external-dns + cert-manager doen alles, geen Cloudflare-actie per tenant
- 🎯 **Wave-rollouts** — canary (wave 0) eerst, dan progressive
- 🔒 **NetworkPolicies meegeleverd** — default-deny per pod, geen impact op co-tenants

## 🏗️ Architectuur

```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Kubernetes Cluster │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────┐ ┌──────────────────────────┐ │
│ │ ns: almere-accept │ │ ns: zuiddrecht-prod │ ... │
│ │ ┌────────────────────┐ │ │ ┌────────────────────┐ │ │
│ │ │ Nextcloud (BE) │ │ │ │ Nextcloud (BE) │ │ │
│ │ │ Nextcloud-base ↑ │ │ │ │ Nextcloud-base ↑ │ │ │
│ │ └────────────────────┘ │ │ └────────────────────┘ │ │
│ │ ┌────────────────────┐ │ │ ┌────────────────────┐ │ │
│ │ │ WOO PWA (FE) │ │ │ │ WOO PWA (FE) │ │ │
│ │ │ React-base ↑ │ │ │ │ React-base ↑ │ │ │
│ │ └────────────────────┘ │ │ └────────────────────┘ │ │
│ └──────────────────────────┘ └──────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ Cluster-infra: external-dns (Cloudflare), cert-manager, ingress-nginx ││
│ └─────────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────┐
│ Cloudflare │ ← auto-sync via external-dns
│ (DNS only) │
└─────────────┘
```

### Waarom dit platform?

Tot nu toe werd elke WOO PWA uitgerold via `mcc create-react <org> <env>`,
een bash-script dat per tenant een ~50-regel Argo Application-manifest
genereerde met alle conventies hardcoded (domein, upstream, nodepool, TLS).
Resultaat: convention-changes raakten *elke* tenant, drift was onzichtbaar,
en "tenant toevoegen" was geen pure GitOps-flow.

Dit platform spiegelt `Nextcloud-base`: één ApplicationSet, gelaagde
Helm-values, één wijziging in `common.yaml` raakt iedereen tegelijk via een
gepland sync window.

## 📁 Repository structuur

```
react-base/
├── charts/
│ └── woo-website/ # Vendored Helm chart (zie UPSTREAM)
├── react-platform/
│ ├── argo/
│ │ ├── projects/ # AppProject met sync windows
│ │ └── applicationsets/ # Tenant-generator
│ ├── platform/
│ │ └── policies/ # README — policies leven in chart-templates
│ ├── values/
│ │ ├── common.yaml # Platform-defaults voor alle tenants
│ │ ├── env/ # accept.yaml, prod.yaml
│ │ ├── tenants/ # tenant-<naam>.yaml (per tenant)
│ │ └── templates/ # tenant-template.yaml
│ ├── scripts/ # validate-values.sh, smoke-checks.sh
│ └── docs/ # ADDING-TENANT, ROLLOUTS, MIGRATION
├── openspec/
│ └── changes/
│ └── bootstrap-react-platform/ # Design proposal voor dit platform
├── CLAUDE.md
├── CHANGELOG.md
└── README.md (dit bestand)
```

## 🚀 Quick Start

### Vereisten
- Kubernetes 1.28+
- Argo CD geïnstalleerd
- `cluster-infra` repo deployed (external-dns + cert-manager + ingress-nginx)
- DNS-zones `commonground.nu` / `openwoo.app` / `opencatalogi.nl` op Cloudflare
- Tenant-namespace bestaat al via `Nextcloud-base`

### 1. Clone
```bash
git clone https://github.com/ConductionNL/React-base.git
cd React-base
```

### 2. Apply AppProject + ApplicationSet
```bash
kubectl apply -f react-platform/argo/projects/react-platform.yaml
kubectl apply -f react-platform/argo/applicationsets/react-tenants.yaml
```

### 3. Eerste tenant
Zie [Tenant toevoegen](#-tenant-toevoegen) hieronder.

## 🪶 Tenant toevoegen

```yaml
# react-platform/values/tenants/tenant-mijn-org.yaml
tenant:
name: mijn-org
environment: accept
```

Dat is genoeg. Commit + push + merge → ApplicationSet pakt het op,
external-dns maakt het Cloudflare-record, cert-manager regelt TLS.

Met branding:

```yaml
tenant:
name: almere
environment: accept
branding:
organisationName: "Gemeente Almere"
themeClassname: almere-theme
footerHideLogo: true
jumbotronImageUrl: "https://..."
faviconUrl: "data:image/png;base64,..."
```

Voor de volledige walkthrough en validatie-stappen: zie
[`react-platform/docs/ADDING-TENANT.md`](react-platform/docs/ADDING-TENANT.md).

## 🌐 Hostname-conventies

| Omgeving | Frontend hostname | Upstream API host |
|---|---|---|
| `accept` | `<naam>.accept.openwoo.app` | `<naam>.accept.commonground.nu` |
| `prod` | `<naam>.openwoo.app` | `<naam>.commonground.nu` |

Override per tenant via `tenant.hostname` of `tenant.apiBaseUrl` (zelden nodig).

## 📚 Documentatie

| Document | Onderwerp |
|---|---|
| [`CLAUDE.md`](CLAUDE.md) | Architectuur en regels — primaire referentie |
| [`react-platform/docs/ADDING-TENANT.md`](react-platform/docs/ADDING-TENANT.md) | Tenant toevoegen, validatie, removal |
| [`react-platform/docs/ROLLOUTS.md`](react-platform/docs/ROLLOUTS.md) | Sync windows, image-bumps, wave-volgorde, rollback |
| [`react-platform/docs/MIGRATION.md`](react-platform/docs/MIGRATION.md) | Cut-over van losse Applications naar deze ApplicationSet |
| [`openspec/changes/bootstrap-react-platform/`](openspec/changes/bootstrap-react-platform/) | Design proposal en taken |

## 🔧 Validatie

```bash
./react-platform/scripts/validate-values.sh # vereiste velden + filename
./react-platform/scripts/smoke-checks.sh # helm template + kubeconform
```

## 📝 License

Onder de MIT License — zie LICENSE.

---

<div align="center">
<sub>Built voor de CommonGround community</sub>
</div>
Loading