Skip to content

Automate release documentation across openemr, website-openemr, openemr-devops #664

Description

@kojiromike

Refines #662 and overlaps with #638.

Goal

Automate the per-release documentation work currently tracked in the wiki (QA and Release Process — Documentation and Steps for an official release) so that the release manager's manual surface shrinks to: edit a generated release-notes draft, sign off on the ONC certification page, and merge three pre-built PRs.

Approach: release-please-shaped, multi-repo

Borrow the release-please pattern (one long-lived PR per release branch, auto-updated on every push, merged to ship) but spread it across the three repos that own different slices of the release. The openemr/openemr PR is the conductor; the other two are downstream and consume its events.

Move docs off the wiki

For docs that are release artifacts (install/upgrade for v8.1.0, OpenAPI spec, EHI/B10 schemaspy output, acknowledgements, release notes, ONC cert page), publish to website-openemr (Hugo, PR-reviewable, version-pinned) and website-openemr-files (large binaries). Leave the wiki for living community references (generic howtos, FAQ).

This eliminates the need for a MediaWiki bot account and makes every release-doc change diffable and reviewable. Redirects from old version-specific URLs become Hugo aliases (one line of frontmatter).

The three PRs

1. openemr/openemr — release-prep PR (the conductor)

Long-lived PR against rel-810, auto-updated on every push. Contains the mechanical changes from the wiki's pre-tag checklist:

  • version.php: strip -dev from $v_tag
  • library/globals.inc.php: allow_debug_language default to 0
  • docker/production/docker-compose.yml: pin image version (vs latest on master)
  • _rest_routes.inc.php: set version + regenerated swagger/openemr-api.yaml (CLI exists: php bin/console openemr:create-api-documentation)
  • docker-version increments (root, sites/default/, docker/.../root/)
  • New fsupgrade-N.sh scaffold + Dockerfile edits in upgrade dir
  • New sql/X_Y_Z-to-X_Y_Z+1_upgrade.sql skeleton on master
  • Refreshed acknowledge_license_cert.html

Merging this PR is the "we're shipping" decision. The workflow then creates an annotated tag on the merge commit (git tag -a or the GitHub API with a tag object — never a lightweight ref). Lightweight tags lack author/date/message metadata and break git describe, release tooling, and downstream consumers that introspect tag objects.

2. website-openemr (+ website-openemr-files) — docs PR

Subscribes to pushes on rel-* in openemr/openemr via repository_dispatch. Long-lived PR per release branch.

Generated content:

  • OpenAPI YAML → committed to website repo
  • EHI/B10 schemaspy HTML tree → pushed to website-openemr-files under files/openemr-<version>-ehi/ (too large for Hugo repo)
  • Install/upgrade pages — Hugo template + version param, replaces wiki copies
  • Acknowledgements — generated from git shortlog v8_0_0..v8_1_0
  • Release-notes draft — milestone PRs grouped by feat: / bug: / refactor: / chore: prefix (matches existing openemr-dev:create-release-change-log CLI conventions)
  • Hugo aliases for legacy URLs (OpenEMR 8 APIOpenEMR 8.1.0 API, etc.)

Each page renders with a release-status shortcode showing DRAFT — based on rel-810 @ <sha> until the conductor's tag event flips it to FINAL. Workflow force-updates the PR branch on every regeneration so history doesn't bloat.

Doesn't gate the tag — consumes it.

3. openemr-devops — infra/test-matrix PR

Per #638's current/next/dev rotation:

  • When rel-810 cuts: next becomes 8.1, current stays 8.0
  • When v8_1_0 tags: current becomes 8.1, drop the prior

Touches CI test matrices, package version refs, raspberrypi/docker pinned versions. Independent of the docs PR; runs in parallel. Should ideally merge before the tag so CI is ready against the new branch.

Workflow trigger DAG

openemr/openemr release-prep PR  ── merge → tag v8_1_0
            │                              │
            └── (push to rel-*) ───────────┼──→ website-openemr docs PR
                                           │
                                           └──→ openemr-devops infra PR

Three workflows total, in three repos, all driven by pushes on rel-* and the eventual tag. No new triggers, no cron, no human handoff between workflows.

Irreducibly manual

Even with full automation, the release manager still:

  1. Triggers the initial branch cut (rel-N).
  2. Edits the auto-generated release-notes draft for tone and what's noteworthy.
  3. Reviews/signs off on the ONC Ambulatory EHR Certification Requirements page.
  4. (Major releases) writes the marketing piece.
  5. Merges three pre-built PRs.

Everything else — artifact builds, install/upgrade page rewrites, redirect setup, version bumps, acknowledgement lists, package version pins — is mechanical and lives in the workflows.

What needs doing

In rough dependency order:

  • Build the conductor workflow in `openemr/openemr` (release-prep PR generator + dispatcher)
  • Build the docs workflow in `website-openemr` (consumer of dispatch, opens/updates docs PR, pushes binaries to `website-openemr-files`)
  • Build the infra workflow here in `openemr-devops` (test-matrix + package version bumps tied to Release process upgrades #638)
  • Hugo templates + shortcodes for install/upgrade pages and the DRAFT/FINAL release-status banner
  • One-time backfill: create Hugo pages for the current shipped version so the redirect chain has a target

Related

Cross-repo contracts

The three plan PRs (openemr/openemr#11896, #665, openemr/website-openemr#82) all consume or emit the same events. Pinned here so the three implementation tracks don't invent slightly-different shapes.

Event names

Emitted by the conductor in openemr/openemr; consumed by openemr-devops and website-openemr (+ website-openemr-files).

Event When
openemr-rel-cut First push to a new rel-* branch
openemr-rel-update Subsequent push to an existing rel-*
openemr-tag Annotated tag created on rel-* HEAD

Payload schema

Common envelope on every event:

{
  "event": "openemr-rel-cut",
  "repo": "openemr/openemr",
  "sha": "<40-char hex>",
  "actor": "<dispatching identity>",
  "dispatched_at": "<ISO8601 UTC>",
  "data": { "...": "event-specific" }
}

Per-event data:

  • openemr-rel-cut / openemr-rel-update:
    { "branch": "rel-810", "version": "8.1.0", "prev_release": "8.0.0" }
  • openemr-tag:
    { "tag": "v8_1_0", "branch": "rel-810", "version": "8.1.0" }

The schema lives at tools/release/contracts/dispatch.schema.json in openemr/openemr-devops (since that repo already has the tools/release/ PHP foundation). Conductor and consumers each pull it in (git submodule, vendored copy, or composer package — TBD per repo).

Naming conventions

Thing Pattern Example
Release branch rel-<MAJOR><MINOR>0 rel-810
Release tag v<MAJOR>_<MINOR>_<PATCH> v8_1_0
Hugo version param <MAJOR>.<MINOR>.<PATCH> 8.1.0
Slot names (devops) current / next / dev
Auto-PR branches release-prep/<rel-branch> (conductor), release-rotation/auto (devops), release-docs/<version> (website)

Tag-object spec

Every release tag is annotated (Git object type tag, not commit).

  • Tagger: the release-automation app/bot identity (TBD which app).
  • Message: must contain the version (8.1.0), the release date (YYYY-MM-DD UTC), and the merge-commit SHA. Suggested template:
    OpenEMR <version> released <YYYY-MM-DD>
    
    Conductor PR: <url>
    Merge commit: <sha>
    
  • Signing: open question. If we go signed, the bot identity needs a signing key; otherwise note in the tag message that automation produced it unsigned.

Tag-shape verifier

Lives in openemr/openemr-devops at tools/release/src/TagVerifier.php (alongside the existing VersionBumper). One copy, consumed by:

  • conductor (openemr/openemr) — verify the tag it just created
  • rotation workflow (openemr-devops) — verify the tag it's reacting to
  • docs workflow (website-openemr) — verify the tag before flipping DRAFT → FINAL

Consumption mechanism: composer package published from tools/release/ (cleanest) or vendored copy (simpler to start). Pick one before any consumer wires it in.

Version regexes

branch: ^rel-(\d+)(\d)0$        # major, minor; patch is always 0 on the branch
tag:    ^v(\d+)_(\d+)_(\d+)$    # major, minor, patch

Resolved contract decisions

Bot identity: A GitHub App (the "release app") is the bot. openemr-devops already has its credentials as repo secrets RELEASE_APP_ID and RELEASE_APP_PRIVATE_KEY. The same secrets must be added to openemr/openemr, openemr/website-openemr, and openemr/website-openemr-files, and the App installed on each.

Signing: Tags are unsigned. Tag message includes the notice Created by openemr-release-bot via automation. Revisit later if maintainers want signed tags.

Code-sharing for dispatch.schema.json and TagVerifier.php: Vendor both into each consumer. Canonical source lives in openemr-devops at tools/release/contracts/dispatch.schema.json and tools/release/src/TagVerifier.php. A drift-check script (tools/release/bin/check-vendored.php) runs in CI on each consumer to fail on divergence.

website-openemr-files sub-dispatch: Uses the same envelope as the conductor events. Event name openemr-docs-binaries. Per-event data:

{ "version": "8.1.0", "branch": "rel-810", "files": ["openemr-8.1.0-ehi.tar.gz", "openemr-8.1.0-b10.tar.gz"] }

Permissions self-check

Each repo's plan includes a release-permissions-check workflow (manual workflow_dispatch) that mints an App token from the secrets and probes only the operations that repo's release workflow performs. Running it after install is the maintainer's verification that the App is correctly scoped before any release runs. See each plan PR for the per-repo probe list.

Maintainer action items (blocking implementation)

  • Install the release App on openemr/openemr, openemr/website-openemr, and openemr/website-openemr-files.
  • Copy RELEASE_APP_ID and RELEASE_APP_PRIVATE_KEY secrets to those three repos.
  • Confirm the App's permissions include contents:write, pull-requests:write, and actions:write (the last is needed by the dispatchers in openemr/openemr and openemr/website-openemr).

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions