From 39756293bffa0f24297dd8c9f8555aaa9329fc65 Mon Sep 17 00:00:00 2001 From: Levi van Noort <73097785+levivannoort@users.noreply.github.com> Date: Thu, 29 Oct 2026 15:17:39 +0100 Subject: [PATCH 1/2] ci: read the Appwrite version from appVersion everywhere - The install workflow reads appVersion once ("Versions under test") for the DNS-check image and the post-upgrade version check, which hardcoded or re-parsed it. - The upgrade test starts from the newest published chart, looked up from the releases (created only after the chart is in the registry), instead of a pinned upgrade-from that each Appwrite upgrade had to bump. - hack/check-versions.sh (make check-versions, in make test and the Chart metadata job) fails when the docs, examples, values, templates, the icon URL or the workflows name an Appwrite version other than appVersion. "X.Y.Z and later" and "X.Y.Z's" are history; so are CHANGELOG.md, the version notes and Chart.yaml's annotations. - It found README's "upgrade from Appwrite 2.2.0", which never shipped, and upgrading.md's pinned CI and downgrade-error versions; those now read without a version. The placeholder-secret error says "2.6.2 and later". - The drift check opens (or comments on) an issue when it fails itself: its 2026-10-26 failure was only visible in the Actions tab. - CONTRIBUTING's upgrade step 5 is make check-versions; no workflow edits. --- .github/workflows/lint.yaml | 2 + .github/workflows/test-install.yaml | 32 ++++++++++---- .github/workflows/upstream-drift.yaml | 15 +++++++ CONTRIBUTING.md | 15 +++---- Makefile | 6 ++- README.md | 2 +- charts/appwrite/templates/_validate.tpl | 2 +- docs/integrations.md | 4 +- docs/upgrading.md | 5 +-- hack/check-versions.sh | 55 +++++++++++++++++++++++++ 10 files changed, 115 insertions(+), 23 deletions(-) create mode 100755 hack/check-versions.sh diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 9fcd047..b178a96 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -169,6 +169,8 @@ jobs: persist-credentials: false - name: artifacthub.io/images matches values.yaml run: hack/chart-images.sh --check + - name: Every Appwrite version named matches appVersion + run: make check-versions artifacthub: name: Artifact Hub lint diff --git a/.github/workflows/test-install.yaml b/.github/workflows/test-install.yaml index 705b51a..1c06ba3 100644 --- a/.github/workflows/test-install.yaml +++ b/.github/workflows/test-install.yaml @@ -29,7 +29,7 @@ concurrency: jobs: install: - name: ${{ matrix.scenario }} (k8s ${{ matrix.kubernetes }})${{ matrix.upgrade-from && format(' upgrade from chart {0}', matrix.upgrade-from) || '' }} + name: ${{ matrix.scenario }} (k8s ${{ matrix.kubernetes }})${{ matrix.upgrade-from && ' upgrade from the latest release' || '' }} runs-on: >- ${{ (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) @@ -52,12 +52,12 @@ jobs: kubernetes: v1.37.0 - scenario: grouped-workers kubernetes: v1.37.0 - # Install a published chart release with its own defaults and + # Install the newest published chart with its own defaults and # Appwrite version, then upgrade to this chart's: the chart upgrade - # and Appwrite's data migration. + # and Appwrite's data migration, as users will run them. - scenario: default kubernetes: v1.37.0 - upgrade-from: 2.1.0 + upgrade-from: latest # PostgreSQL, Redis and ClickHouse outside the release. - scenario: external-dbs kubernetes: v1.37.0 @@ -80,6 +80,23 @@ jobs: - uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1 with: version: v4.3.0 + - name: Versions under test + env: + GH_TOKEN: ${{ github.token }} + UPGRADE: ${{ matrix.upgrade-from }} + run: | + app_version=$(awk '/^appVersion:/ {gsub(/"/, "", $2); print $2}' charts/appwrite/Chart.yaml) + echo "APP_VERSION=${app_version}" >> "$GITHUB_ENV" + echo "Appwrite ${app_version}" + if [[ "$UPGRADE" == latest ]]; then + # Highest chart version with a release; the release workflow + # creates it only after the chart is in the registry. + from=$(gh release list --repo "$GITHUB_REPOSITORY" --exclude-drafts --exclude-pre-releases --limit 100 \ + --json tagName --jq '.[].tagName | select(startswith("appwrite-")) | ltrimstr("appwrite-")' | sort -V | tail -1) + [[ -n "$from" ]] || { echo "::error::No published chart release to upgrade from"; exit 1; } + echo "UPGRADE_FROM=${from}" >> "$GITHUB_ENV" + echo "::notice::Upgrading from chart ${from}" + fi - uses: helm/kind-action@06c1ae10762d3b9c1644e7fe69596ae519e015a2 # v1.15.0 with: cluster_name: appwrite @@ -108,18 +125,17 @@ jobs: # instead of as a 15-minute install timeout. run: | # shellcheck disable=SC2016 # expanded inside the pod - kubectl -n appwrite run dns-check --rm -i --restart=Never --image=docker.io/appwrite/appwrite:2.6.2 --command -- sh -c ' + kubectl -n appwrite run dns-check --rm -i --restart=Never --image="docker.io/appwrite/appwrite:${APP_VERSION}" --command -- sh -c ' cat /etc/resolv.conf ok=0 for host in postgres.external-dbs.svc.cluster.local postgres.external-dbs.svc postgres.external-dbs; do if getent hosts "$host"; then echo "resolved $host"; else echo "FAILED $host"; ok=1; fi done exit $ok' - - name: Install chart ${{ matrix.upgrade-from }} + - name: Install the latest published chart if: matrix.upgrade-from env: SCENARIO: ${{ matrix.scenario }} - UPGRADE_FROM: ${{ matrix.upgrade-from }} run: | helm upgrade --install appwrite oci://ghcr.io/appwrite/charts/appwrite --version "${UPGRADE_FROM}" \ -n appwrite --create-namespace -f "charts/appwrite/ci/${SCENARIO}-values.yaml" --wait --timeout 15m @@ -131,7 +147,7 @@ jobs: - name: Check the API runs the new version (the migration hook must succeed for the upgrade to pass) if: matrix.upgrade-from run: | - kubectl -n appwrite get deployment appwrite-api -o jsonpath='{.metadata.annotations.appwrite/version}' | grep -qx "$(awk '/^appVersion:/ {gsub(/"/, "", $2); print $2}' charts/appwrite/Chart.yaml)" + kubectl -n appwrite get deployment appwrite-api -o jsonpath='{.metadata.annotations.appwrite/version}' | grep -qx "$APP_VERSION" - name: helm test run: make helm-test - name: Smoke test diff --git a/.github/workflows/upstream-drift.yaml b/.github/workflows/upstream-drift.yaml index 3775466..e83da94 100644 --- a/.github/workflows/upstream-drift.yaml +++ b/.github/workflows/upstream-drift.yaml @@ -58,3 +58,18 @@ jobs: echo "https://github.com/appwrite/appwrite/compare/${current}...${latest}" } > body.md gh issue create --title "$title" --body-file body.md + # A failed scheduled run shows up nowhere else, and it hides the + # release it was looking at. + - name: Open an issue when the check fails + if: failure() + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + title="Upstream drift check is failing" + number=$(gh issue list --state open --search "in:title \"${title}\"" --json number --jq '.[0].number // empty') + if [[ -n "$number" ]]; then + gh issue comment "$number" --body "Failed again: ${RUN_URL}" + else + gh issue create --title "$title" --body "The weekly check for new Appwrite releases failed, so a release may be going unnoticed. Run: ${RUN_URL}" + fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a03322d..ab6d47a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -94,8 +94,8 @@ The scenarios CI installs are the `charts/appwrite/ci/*-values.yaml` files. | Workflow | When | What | | --- | --- | --- | -| [Lint and test](.github/workflows/lint.yaml) | every pull request and push to `main` | `ct lint` and `helm lint --strict` for every `ci/` scenario, unit tests (Helm 3.18 and 4.3), kubeconform, `make examples`, generated files, `artifacthub.io/images`, `ah lint`, a `CHANGELOG.md` entry for chart changes and `make check-changes` | -| [Install](.github/workflows/test-install.yaml) | changes to `charts/`, `hack/`, the `Makefile` or the workflow | installs on kind per scenario, `helm test`, `hack/smoke.sh`, routes through Traefik, an upgrade from the previous Appwrite release, and a second upgrade that must keep credentials and pods; `Install result` sums up the scenarios | +| [Lint and test](.github/workflows/lint.yaml) | every pull request and push to `main` | `ct lint` and `helm lint --strict` for every `ci/` scenario, unit tests (Helm 3.18 and 4.3), kubeconform, `make examples`, generated files, `artifacthub.io/images`, Appwrite versions named in the docs (`make check-versions`), `ah lint`, a `CHANGELOG.md` entry for chart changes and `make check-changes` | +| [Install](.github/workflows/test-install.yaml) | changes to `charts/`, `hack/`, the `Makefile` or the workflow | installs on kind per scenario, `helm test`, `hack/smoke.sh`, routes through Traefik, an upgrade from the newest published chart, and a second upgrade that must keep credentials and pods; `Install result` sums up the scenarios | | [Workflow lint](.github/workflows/actions-lint.yaml) | changes to workflows or `hack/*.sh` | actionlint, shellcheck and zizmor (`make lint-actions` locally) | | [Renovate generate](.github/workflows/renovate-generate.yaml) | Renovate pull requests that change `values.yaml` or `Chart.yaml` | runs `make generate` and commits the result, see [below](#renovate-pull-requests) | | [Nightly end-to-end](.github/workflows/e2e-nightly.yaml) | daily | `ci/full-values.yaml` (every optional component but embedding) with Functions built and executed in the cluster | @@ -162,11 +162,12 @@ also opens a pull request for `appVersion`). To follow it: `templates/workers/_workers.tpl` and by `workers.queues`), and new tasks. 4. Check whether the data migration changed (`src/Appwrite/Migration/Migration.php`) and update [docs/upgrading.md](docs/upgrading.md) if it did. -5. Find what else names the old version, such as the docs, the chart icon - URL and the DNS check in the install workflow, with - `git grep -n `. Set `upgrade-from` in - [test-install.yaml](.github/workflows/test-install.yaml) to the version - the chart shipped until now. +5. Run `make check-versions`. It lists the docs, examples, values and the + chart icon URL that still name the old version. Recheck each claim against + the new version and update it, or write "Appwrite X.Y.Z and later" when + it's about the version that introduced something. The workflows need + nothing: they read `appVersion`, and the upgrade test starts from the + newest published chart. 6. Run `make generate test`, then test the upgrade on kind the way CI does: ```console diff --git a/Makefile b/Makefile index 6ae8a29..c0981c1 100644 --- a/Makefile +++ b/Makefile @@ -83,6 +83,10 @@ changes: ## Write the CHANGELOG.md section of the chart version into artifacthub check-changes: ## Fail if artifacthub.io/changes doesn't match CHANGELOG.md (no-op until CHANGELOG.md has a section for the chart version) hack/changelog-changes.sh --check +.PHONY: check-versions +check-versions: ## Fail if the docs, examples or workflows name an Appwrite version other than appVersion + hack/check-versions.sh + .PHONY: kubeconform kubeconform: ## Validate rendered manifests against several Kubernetes versions @for f in $(CHART)/ci/*-values.yaml; do \ @@ -115,7 +119,7 @@ template: ## Render the chart with default values helm template $(RELEASE) $(CHART) -n $(NAMESPACE) .PHONY: test -test: lint unittest kubeconform examples check-generated check-changes ## Run every static check +test: lint unittest kubeconform examples check-generated check-changes check-versions ## Run every static check .PHONY: kind-up kind-up: ## Create a local kind cluster diff --git a/README.md b/README.md index 3d37dde..d2a7543 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ that enforces the `restricted` profile: | Scenario | What it exercises | | --- | --- | | `default` | Built-in datastores and combined workers, on Kubernetes 1.29 and 1.37 | -| `default` | The data migration on upgrade, upgrade from Appwrite 2.2.0 | +| `default` | The chart upgrade and data migration, from the latest published chart | | `hardened` | NetworkPolicies with restricted egress | | `separate-workers`, `grouped-workers` | Worker topologies | | `external-dbs` | PostgreSQL, Redis and ClickHouse outside the release | diff --git a/charts/appwrite/templates/_validate.tpl b/charts/appwrite/templates/_validate.tpl index fda812e..d5ca873 100644 --- a/charts/appwrite/templates/_validate.tpl +++ b/charts/appwrite/templates/_validate.tpl @@ -122,7 +122,7 @@ configmap-env.yaml so it also runs under `helm template`. {{- $errors = append $errors "compute.executor.existingSecret is required when compute.secrets.existingSecret or an S3 secretName (storage.s3.accessKeyId, storage.s3.secretAccessKey) is set: the executor runs in another namespace and can't read them. Create a Secret there with the keys executor-secret, s3-access-key and s3-secret-key." }} {{- end }} {{- if and (not $v.compute.secrets.existingSecret) (eq $v.compute.secrets.orchestratorSecret "your-secret-key") }} -{{- $errors = append $errors "compute.secrets.orchestratorSecret is Appwrite's public placeholder, which Appwrite 2.6.2 refuses for job callbacks, so every build would fail. Leave it empty for the chart to generate one, or set a random value." }} +{{- $errors = append $errors "compute.secrets.orchestratorSecret is Appwrite's public placeholder, which Appwrite 2.6.2 and later refuse for job callbacks, so every build would fail. Leave it empty for the chart to generate one, or set a random value." }} {{- end }} {{- if ne $storage.device "s3" }} {{- $errors = append $errors "compute.enabled needs storage.device=s3: builds and the executor exchange code and build output through object storage. Keep the default (s3, with the built-in object store), or set compute.enabled=false to store files on the local volume without Functions and Sites." }} diff --git a/docs/integrations.md b/docs/integrations.md index bf9ca82..a32a294 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -216,8 +216,8 @@ appwrite: Upgrade, then open a function or site in the console and choose *Connect Git*. If GitHub shows a delivery error under the app's *Advanced* tab, check that `https:///v1/vcs/github/events` is reachable from outside and -that the webhook secret matches. The secret is required: Appwrite 2.4.0 and -later refuse GitHub's events without `_APP_VCS_GITHUB_WEBHOOK_SECRET`. +that the webhook secret matches. The secret is required: Appwrite 2.4.0 and later +refuse GitHub's events without `_APP_VCS_GITHUB_WEBHOOK_SECRET`. ### GitLab, Gitea and Bitbucket diff --git a/docs/upgrading.md b/docs/upgrading.md index f9b237c..96604af 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -16,8 +16,7 @@ The [changelog](../CHANGELOG.md) lists what changed in each chart version, and [version notes](#version-notes) what you have to do about it. What CI tests: a fresh install of the `appVersion`, and an upgrade from the -published chart 2.1.0 (Appwrite 2.6.1) with default values, including the -data migration. +latest published chart with default values, including the data migration. ## What happens during an upgrade @@ -117,7 +116,7 @@ before upgrading. running: ```text -Appwrite 2.3.0 is installed and this would downgrade it to 2.2.0. Appwrite can't migrate data backwards; restore a backup taken before the upgrade instead, or set migrations.allowDowngrade=true. +Appwrite is installed and this would downgrade it to . Appwrite can't migrate data backwards; restore a backup taken before the upgrade instead, or set migrations.allowDowngrade=true. ``` The check reads the running version with `lookup`, so it does nothing with diff --git a/hack/check-versions.sh b/hack/check-versions.sh new file mode 100755 index 0000000..7338bc7 --- /dev/null +++ b/hack/check-versions.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# Fails when the docs, examples, values, templates or workflows name an +# Appwrite version other than appVersion in charts/appwrite/Chart.yaml, so an +# upgrade can't leave a claim about the old version behind. +# +# It looks at "Appwrite X.Y.Z", the appwrite/appwrite:X.Y.Z image and links +# into the appwrite/appwrite repository at a tag. History is allowed when it +# says so: "Appwrite 2.4.0 and later", "or later", or "Appwrite 2.4.0's ...". +# Not checked: CHANGELOG.md, the version notes in docs/upgrading.md, +# Chart.yaml's annotations, the unit tests (which use made-up versions) and +# generated files. +# +# Usage: hack/check-versions.sh +set -euo pipefail + +CHART="${CHART:-charts/appwrite}" +app_version=$(awk '/^appVersion:/ {gsub(/"/, "", $2); print $2}' "$CHART/Chart.yaml") +[[ -n "$app_version" ]] || { echo "No appVersion in $CHART/Chart.yaml" >&2; exit 2; } + +v='[0-9]+\.[0-9]+\.[0-9]+' +pattern="Appwrite ${v}('s| and later| or later)?|appwrite/appwrite:${v}|appwrite/appwrite/(blob/|tree/|raw/)?${v}/" + +# Prints the line from which a file is history, if it has one: the version +# notes of docs/upgrading.md, and Chart.yaml's annotations (its changes are +# history, and hack/chart-images.sh checks its images). +history_from() { + case "$1" in + docs/upgrading.md) grep -n '^## Version notes' "$1" | cut -d: -f1 ;; + "$CHART/Chart.yaml") grep -n '^annotations:' "$1" | cut -d: -f1 ;; + esac +} + +stale=0 +while IFS= read -r file; do + history=$(history_from "$file" || true) + while IFS= read -r hit; do + line=${hit%%:*} match=${hit#*:} + case "$match" in *"'s" | *" and later" | *" or later") continue ;; esac + [[ -n "$history" && "$line" -ge "$history" ]] && continue + [[ "$match" =~ ($v) ]] + if [[ "${BASH_REMATCH[1]}" != "$app_version" ]]; then + echo "${file}:${line}: ${match} (the chart ships Appwrite ${app_version})" + stale=1 + fi + done < <(grep -noE "$pattern" "$file" || true) +done < <(git ls-files README.md CONTRIBUTING.md RELEASING.md SECURITY.md 'docs/*.md' 'examples/*' \ + "$CHART/Chart.yaml" "$CHART/README.md.gotmpl" "$CHART/values.yaml" "$CHART/templates/*" "$CHART/files/*" \ + '.github/workflows/*' 'hack/*.sh') + +if [[ "$stale" -ne 0 ]]; then + echo >&2 + echo "Update these to Appwrite ${app_version}, or say which versions a claim covers (\"and later\")." >&2 + exit 1 +fi +echo "Every Appwrite version named matches appVersion ${app_version}." From 8170f71cc1157e29f99ca92f5381f0c93aaed307 Mon Sep 17 00:00:00 2001 From: Levi van Noort <73097785+levivannoort@users.noreply.github.com> Date: Thu, 29 Oct 2026 15:28:17 +0100 Subject: [PATCH 2/2] ci: don't upgrade from a release of the commit under test On a release push to main, the release workflow can publish the new chart before this lookup runs, and the upgrade leg would then upgrade that chart to itself. Releases target GITHUB_SHA, so skip the one whose tag points at the checkout; between releases, the published chart of the current version is still the one to upgrade from. From review on #31. --- .github/workflows/test-install.yaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-install.yaml b/.github/workflows/test-install.yaml index 1c06ba3..f322f94 100644 --- a/.github/workflows/test-install.yaml +++ b/.github/workflows/test-install.yaml @@ -90,9 +90,12 @@ jobs: echo "Appwrite ${app_version}" if [[ "$UPGRADE" == latest ]]; then # Highest chart version with a release; the release workflow - # creates it only after the chart is in the registry. - from=$(gh release list --repo "$GITHUB_REPOSITORY" --exclude-drafts --exclude-pre-releases --limit 100 \ - --json tagName --jq '.[].tagName | select(startswith("appwrite-")) | ltrimstr("appwrite-")' | sort -V | tail -1) + # creates it only after the chart is in the registry. A release + # of this very commit (the release workflow can finish first on a + # push to main) would upgrade the chart to itself, so it's skipped. + from=$(gh api "repos/${GITHUB_REPOSITORY}/releases?per_page=100" \ + --jq '.[] | select((.draft or .prerelease or .target_commitish == env.GITHUB_SHA) | not) + | .tag_name | select(startswith("appwrite-")) | ltrimstr("appwrite-")' | sort -V | tail -1) [[ -n "$from" ]] || { echo "::error::No published chart release to upgrade from"; exit 1; } echo "UPGRADE_FROM=${from}" >> "$GITHUB_ENV" echo "::notice::Upgrading from chart ${from}"