From 945d2225a2d256183c08bfbe6b942302f8ac343b Mon Sep 17 00:00:00 2001 From: Paul Payne Date: Sun, 24 May 2026 04:00:07 +0000 Subject: [PATCH] First version of app upgrade. --- ADDING-APPS.md | 112 ++++++++++++++++++ .../.versions/1.0.0-1/db-init-job.yaml | 72 +++++++++++ .../.versions/1.0.0-1/deployment.yaml | 55 +++++++++ .../.versions/1.0.0-1/kustomization.yaml | 15 +++ e2e-test-app/.versions/1.0.0-1/manifest.yaml | 23 ++++ e2e-test-app/.versions/1.0.0-1/namespace.yaml | 4 + e2e-test-app/.versions/1.0.0-1/pvc.yaml | 11 ++ e2e-test-app/.versions/1.0.0-1/service.yaml | 11 ++ e2e-test-app/manifest.yaml | 11 +- 9 files changed, 313 insertions(+), 1 deletion(-) create mode 100644 e2e-test-app/.versions/1.0.0-1/db-init-job.yaml create mode 100644 e2e-test-app/.versions/1.0.0-1/deployment.yaml create mode 100644 e2e-test-app/.versions/1.0.0-1/kustomization.yaml create mode 100644 e2e-test-app/.versions/1.0.0-1/manifest.yaml create mode 100644 e2e-test-app/.versions/1.0.0-1/namespace.yaml create mode 100644 e2e-test-app/.versions/1.0.0-1/pvc.yaml create mode 100644 e2e-test-app/.versions/1.0.0-1/service.yaml diff --git a/ADDING-APPS.md b/ADDING-APPS.md index cae0849..f9f8c61 100644 --- a/ADDING-APPS.md +++ b/ADDING-APPS.md @@ -85,6 +85,118 @@ Wild Cloud uses a two-part version scheme inspired by Debian packaging: `=3.5.0" # Can upgrade directly from 3.5.x + - version: ">=3.4.0" + via: "3.5.3-1" # Must pass through 3.5.x first + - version: "<3.4.0" + blocked: true + notes: "Requires sequential major upgrades. See upstream docs." + preUpgrade: + backup: required # "none", "recommended", or "required" + migrations: + pre: + - migrations/pre-deploy.yaml # K8s Job YAML paths relative to app dir + post: + - migrations/post-deploy.yaml + configMigrations: + oldKeyName: newKeyName # Renames config keys automatically +``` + +**Fields:** + +| Field | Description | +|-------|-------------| +| `from` | List of version constraint rules, evaluated in order (first match wins) | +| `from[].version` | Version constraint: `>=`, `>`, `<=`, `<`, `=`, or `>0` (matches any) | +| `from[].via` | Waypoint version in `.versions/` — upgrade must pass through this version first | +| `from[].blocked` | If true, upgrade is blocked with an error message | +| `from[].notes` | Human-readable message shown when blocked or as context | +| `preUpgrade.backup` | Backup requirement: `"required"` blocks upgrade until backup is done, `"recommended"` shows a warning | +| `migrations.pre` | K8s Job YAMLs to run before deploying each version step | +| `migrations.post` | K8s Job YAMLs to run after deploying each version step | +| `configMigrations` | Map of old config key → new config key for automatic renaming | + +#### Waypoint versions (`.versions/` directory) + +When an upgrade requires passing through an intermediate version, store that version's files in a `.versions/` subdirectory: + +``` +myapp/ +├── manifest.yaml # Latest version (e.g., 3.6.0) +├── kustomization.yaml +├── *.yaml +└── .versions/ + └── 3.5.3-1/ # Waypoint version + ├── manifest.yaml # version: 3.5.3-1 (with its own upgrade rules) + ├── kustomization.yaml + └── *.yaml +``` + +Each waypoint is a complete app package. The system computes a chain automatically — for example, upgrading from 3.4.0 to 3.6.0 might produce: `3.4.0 → 3.5.3-1 → 3.6.0`. + +**Creating a waypoint:** + +```bash +mkdir -p wild-directory/myapp/.versions +rsync -a --exclude='.versions' wild-directory/myapp/ wild-directory/myapp/.versions/3.5.3-1/ +# Now update wild-directory/myapp/manifest.yaml to the new version + upgrade rules +``` + +#### Migration jobs + +Migration jobs are K8s Job manifests that run database migrations or other one-time tasks during an upgrade step. They must be **idempotent** (safe to re-run) since a failed upgrade might be retried. + +Place migration job files in the waypoint or app directory and reference them from the `migrations` field: + +```yaml +# migrations/db-migrate.yaml +apiVersion: batch/v1 +kind: Job +metadata: + name: myapp-db-migrate +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: migrate + image: myapp:3.6.0 + command: ["bundle", "exec", "rake", "db:migrate"] +``` + +Each migration step belongs to the version that introduces the breaking change. If version 3.6.0 requires a schema migration, the migration lives in the 3.6.0 manifest (or its waypoint), not on 3.5.x. + +#### Example: simple app with waypoint + +```yaml +# myapp/manifest.yaml (version 2.0.0) +version: 2.0.0 +upgrade: + from: + - version: ">=1.0.0" + via: "1.0.0-1" + - version: "<1.0.0" + blocked: true + notes: "Versions before 1.0.0 are not supported" + preUpgrade: + backup: recommended +``` + +This creates a 2-step upgrade path: `1.x → 1.0.0-1 → 2.0.0`. The waypoint at `.versions/1.0.0-1/` has no `upgrade` block, so it accepts any version directly. + ### Dependency Configuration - Each dependency in `requires` can have: diff --git a/e2e-test-app/.versions/1.0.0-1/db-init-job.yaml b/e2e-test-app/.versions/1.0.0-1/db-init-job.yaml new file mode 100644 index 0000000..d149485 --- /dev/null +++ b/e2e-test-app/.versions/1.0.0-1/db-init-job.yaml @@ -0,0 +1,72 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: e2e-test-app-db-init + labels: + component: db-init +spec: + template: + metadata: + labels: + component: db-init + spec: + restartPolicy: OnFailure + securityContext: + runAsNonRoot: true + runAsUser: 999 + runAsGroup: 999 + seccompProfile: + type: RuntimeDefault + containers: + - name: postgres-init + image: postgres:15 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: false + env: + - name: PGHOST + value: {{ .db.host }} + - name: PGUSER + value: postgres + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: e2e-test-app-secrets + key: postgres.password + - name: DB_NAME + value: {{ .db.name }} + - name: DB_USER + value: {{ .db.user }} + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: e2e-test-app-secrets + key: dbPassword + command: + - /bin/bash + - -c + - | + set -e + echo "Waiting for PostgreSQL to be ready..." + until pg_isready; do + echo "PostgreSQL is not ready - sleeping" + sleep 2 + done + echo "PostgreSQL is ready" + + echo "Creating database and user..." + psql -c "CREATE DATABASE ${DB_NAME};" || echo "Database ${DB_NAME} already exists" + psql -c "CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';" || echo "User ${DB_USER} already exists" + psql -c "ALTER USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';" + psql -c "GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO ${DB_USER};" + psql -d ${DB_NAME} -c "GRANT ALL ON SCHEMA public TO ${DB_USER};" + + echo "Creating test data table..." + psql -d ${DB_NAME} -c "CREATE TABLE IF NOT EXISTS e2e_test_data (id SERIAL PRIMARY KEY, key TEXT UNIQUE NOT NULL, value TEXT NOT NULL, created_at TIMESTAMP DEFAULT NOW());" + psql -d ${DB_NAME} -c "GRANT ALL ON TABLE e2e_test_data TO ${DB_USER};" + psql -d ${DB_NAME} -c "GRANT USAGE, SELECT ON SEQUENCE e2e_test_data_id_seq TO ${DB_USER};" + + echo "Database initialization complete" diff --git a/e2e-test-app/.versions/1.0.0-1/deployment.yaml b/e2e-test-app/.versions/1.0.0-1/deployment.yaml new file mode 100644 index 0000000..31e605b --- /dev/null +++ b/e2e-test-app/.versions/1.0.0-1/deployment.yaml @@ -0,0 +1,55 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: e2e-test-app +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + component: web + template: + metadata: + labels: + component: web + spec: + securityContext: + runAsNonRoot: true + runAsUser: 101 + runAsGroup: 101 + fsGroup: 101 + seccompProfile: + type: RuntimeDefault + containers: + - name: nginx + image: nginxinc/nginx-unprivileged:alpine + ports: + - containerPort: 8080 + name: http + volumeMounts: + - name: app-data + mountPath: /data + resources: + limits: + cpu: 100m + memory: 64Mi + requests: + cpu: 50m + memory: 32Mi + readinessProbe: + httpGet: + path: / + port: 8080 + initialDelaySeconds: 3 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: false + volumes: + - name: app-data + persistentVolumeClaim: + claimName: e2e-test-app-data diff --git a/e2e-test-app/.versions/1.0.0-1/kustomization.yaml b/e2e-test-app/.versions/1.0.0-1/kustomization.yaml new file mode 100644 index 0000000..7ada37d --- /dev/null +++ b/e2e-test-app/.versions/1.0.0-1/kustomization.yaml @@ -0,0 +1,15 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: e2e-test-app +labels: + - includeSelectors: true + pairs: + app: e2e-test-app + managedBy: kustomize + partOf: wild-cloud +resources: + - namespace.yaml + - deployment.yaml + - service.yaml + - pvc.yaml + - db-init-job.yaml diff --git a/e2e-test-app/.versions/1.0.0-1/manifest.yaml b/e2e-test-app/.versions/1.0.0-1/manifest.yaml new file mode 100644 index 0000000..d2d29ab --- /dev/null +++ b/e2e-test-app/.versions/1.0.0-1/manifest.yaml @@ -0,0 +1,23 @@ +name: e2e-test-app +is: e2e-test-app +description: End-to-end test application for automated integration testing. Includes PVC and PostgreSQL dependency to exercise all backup strategies. +version: 1.0.0-1 +requires: + - name: postgres +defaultConfig: + namespace: e2e-test-app + domain: e2e-test-app.{{ .cloud.domain }} + externalDnsDomain: '{{ .cloud.domain }}' + tlsSecretName: wildcard-wild-cloud-tls + storage: 1Gi + db: + host: '{{ .apps.postgres.host }}' + port: '{{ .apps.postgres.port }}' + name: e2e_test_app + user: e2e_test_app +defaultSecrets: + - key: dbPassword + - key: dbUrl + default: "postgres://{{ .app.db.user }}:{{ .secrets.dbPassword }}@{{ .app.db.host }}:{{ .app.db.port }}/{{ .app.db.name }}?sslmode=disable" +requiredSecrets: + - postgres.password diff --git a/e2e-test-app/.versions/1.0.0-1/namespace.yaml b/e2e-test-app/.versions/1.0.0-1/namespace.yaml new file mode 100644 index 0000000..054927e --- /dev/null +++ b/e2e-test-app/.versions/1.0.0-1/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: {{ .namespace }} diff --git a/e2e-test-app/.versions/1.0.0-1/pvc.yaml b/e2e-test-app/.versions/1.0.0-1/pvc.yaml new file mode 100644 index 0000000..ed50743 --- /dev/null +++ b/e2e-test-app/.versions/1.0.0-1/pvc.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: e2e-test-app-data +spec: + accessModes: + - ReadWriteOnce + storageClassName: longhorn + resources: + requests: + storage: {{ .storage }} diff --git a/e2e-test-app/.versions/1.0.0-1/service.yaml b/e2e-test-app/.versions/1.0.0-1/service.yaml new file mode 100644 index 0000000..2fca9c7 --- /dev/null +++ b/e2e-test-app/.versions/1.0.0-1/service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: e2e-test-app +spec: + selector: + component: web + ports: + - port: 80 + targetPort: 8080 + name: http diff --git a/e2e-test-app/manifest.yaml b/e2e-test-app/manifest.yaml index d2d29ab..1739fd9 100644 --- a/e2e-test-app/manifest.yaml +++ b/e2e-test-app/manifest.yaml @@ -1,7 +1,16 @@ name: e2e-test-app is: e2e-test-app description: End-to-end test application for automated integration testing. Includes PVC and PostgreSQL dependency to exercise all backup strategies. -version: 1.0.0-1 +version: 2.0.0 +upgrade: + from: + - version: ">=1.0.0" + via: "1.0.0-1" + - version: "<1.0.0" + blocked: true + notes: "Versions before 1.0.0 are not supported for upgrade" + preUpgrade: + backup: recommended requires: - name: postgres defaultConfig: