Feature/add symlink and sql flows - #5
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an “Atomic Deploy” flow for WordPress deployments that can (a) run SQL migrations against a copied table prefix and then switch wp-config.php to the new prefix, and (b) atomically re-point plugin/theme symlinks to a release directory for fast rollbacks. This is implemented via new reusable workflow(s), a composite action that runs the remote swap script over SSH, and migration script templates, with README documentation updates.
Changes:
- Added
templates/migrations/swap.sh+templates/migrations/migrate.shto support prefix-copy migrations and symlink swapping during deploys. - Added
actions/swap-and-migratecomposite action and.github/workflows/atomic-deploy.yamlreusable workflow to run the remote deploy step and send notifications. - Expanded
README.mddocumentation for Atomic Deploy, rsync/FTP deploys, and templates.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
templates/migrations/swap.sh |
Remote deploy script: preflight, migration/prefix-swap, symlink swap, and release pruning logic. |
templates/migrations/migrate.sh |
Migration runner that applies pending SQL files and records them in a tracking table. |
actions/swap-and-migrate/action.yml |
Composite action that SSHes to the server to run swap.sh, then emails on failure based on exit codes. |
.github/workflows/atomic-deploy.yaml |
Reusable workflow wiring SSH setup + swap-and-migrate action with SMTP/notification secrets. |
README.md |
Documents the new Atomic Deploy flow and updates other workflow/action documentation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 10 changed files in this pull request and generated 4 comments.
Suppressed comments (11)
.github/workflows/deploy.yaml:97
- This reusable workflow will remain pinned to the feature branch after merge. If that temporary branch is deleted, every deployment fails while resolving the action. Point the action back to the stable
mainref.
- uses: pie/.github/actions/deploy-via-rsync@feature/add-symlink-and-sql-flows
.github/workflows/atomic-deploy.yaml:64
- The new reusable workflow is pinned to the temporary feature branch. Once that branch is deleted after merge, atomic deployments fail before connecting to the server. Use the stable
mainref.
- uses: pie/.github/actions/add-ssh-config@feature/add-symlink-and-sql-flows
.github/workflows/deploy.yaml:88
- This reusable workflow will remain pinned to the feature branch after merge. If that temporary branch is deleted, password-based deployments fail while resolving the action. Point the action back to the stable
mainref.
- uses: pie/.github/actions/add-ssh-pass@feature/add-symlink-and-sql-flows
.github/workflows/atomic-deploy.yaml:72
- The core atomic-deploy action is pinned to the temporary feature branch, so callers of the merged workflow will fail to resolve it if the branch is deleted. Use the stable
mainref.
- uses: pie/.github/actions/swap-and-migrate@feature/add-symlink-and-sql-flows
.github/workflows/deploy.yaml:79
- This reusable workflow will remain pinned to the feature branch after merge. If that temporary branch is deleted, every rsync deployment fails while resolving the action. Point the action back to the stable
mainref.
- uses: pie/.github/actions/add-ssh-config@feature/add-symlink-and-sql-flows
actions/swap-and-migrate/scripts/swap.sh:341
- Without pending migrations, maintenance mode is never enabled and
SAFE_TO_RECOVERremains true. A failure after moving one live component (or between these twomvcalls) can therefore leave a missing or mixed-version deployment, yet cleanup neither restores.previousnor marks the state urgent. Keep prior directories until all swaps succeed and add rollback handling for partially completed component swaps (or otherwise enter a protected maintenance state before the first live rename).
if [ -e "$LIVE_PATH" ] || [ -L "$LIVE_PATH" ]; then
mv "$LIVE_PATH" "$OLD_PATH"
fi
mv "$STAGING_PATH" "$LIVE_PATH"
rm -rf "$OLD_PATH"
actions/swap-and-migrate/scripts/swap.sh:272
- Each table is copied independently with no transaction or table lock. Maintenance mode does not stop in-flight requests, WP-CLI jobs, or other database clients, so concurrent writes can produce a new prefix containing an inconsistent cross-table snapshot before it becomes live. Take the clone from a consistent snapshot or hold appropriate locks for the copy.
wp db query "INSERT INTO \`$NEW_TABLE\` SELECT * FROM \`$TABLE\`" --path="$WP_ROOT"
actions/swap-and-migrate/scripts/swap.sh:25
- The 53-character cap does not account for the table prefix or the 11-character
_migrationssuffix. For a 53-character repository slug, even a one-character live prefix produces a 65-character identifier, exceeding MySQL's 64-character table-name limit; the new SHA prefix is longer still. Derive the maximum slug length from the active/new prefixes and reject or deterministically shorten longer names.
REPO_SLUG="$(printf '%s' "$REPO_NAME" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/_/g' | cut -c1-53)"
actions/swap-and-migrate/action.yml:102
- Only exit codes 1 and 2 have notification steps. Other normal failure statuses—such as rsync's 23 or SSH's 255—skip both emails even though the job fails, so the promised failure notification is absent and remote state may be unknown. Normalize all script failures into defined states or add a third notification path for unclassified failures.
- name: Send failure notification — site recovered
if: steps.swap.outcome == 'failure' && steps.swap.outputs.ssh_exit == '1' && inputs.smtp-server != '' && inputs.smtp-username != '' && inputs.smtp-password != '' && inputs.notify-email != ''
.github/workflows/atomic-deploy.yaml:30
- The implementation defaults to
$WP_ROOT/releases, which is a subdirectory insidewp-root, not a sibling. This public input description can make callers configure access controls or paths incorrectly.
description: "Absolute path to the releases directory on the server. Defaults to a 'releases' sibling of wp-root when not set."
README.md:332
- This path uses the full 40-character SHA, but
atomic-deploy.yamlcomputes an 8-character SHA andswap.shlooks under that short-SHA directory. Following this instruction makes the atomic step fail with “Release directory ... not found.” Use the setup workflow output shown in the complete example above.
The calling workflow should rsync `migrations/` to `releases/${{ github.sha }}/migrations` and pass the component list to the `atomic-deploy` workflow:
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…ithub into feature/add-symlink-and-sql-flows
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 10 changed files in this pull request and generated no new comments.
Suppressed comments (10)
.github/workflows/deploy.yaml:79
- These reusable workflows reference actions on a feature branch ref (
@feature/add-symlink-and-sql-flows). After the PR merges, that branch ref may be deleted or moved, breaking downstream repos that use this workflow. Other workflows in this repo pin to@main(e.g..github/workflows/synchronise.yaml:34).
- uses: pie/.github/actions/add-ssh-config@feature/add-symlink-and-sql-flows
.github/workflows/deploy.yaml:88
- These reusable workflows reference actions on a feature branch ref (
@feature/add-symlink-and-sql-flows). After the PR merges, that branch ref may be deleted or moved, breaking downstream repos that use this workflow. Other workflows in this repo pin to@main(e.g..github/workflows/synchronise.yaml:34).
- uses: pie/.github/actions/add-ssh-pass@feature/add-symlink-and-sql-flows
.github/workflows/deploy.yaml:97
- These reusable workflows reference actions on a feature branch ref (
@feature/add-symlink-and-sql-flows). After the PR merges, that branch ref may be deleted or moved, breaking downstream repos that use this workflow. Other workflows in this repo pin to@main(e.g..github/workflows/synchronise.yaml:34).
- uses: pie/.github/actions/deploy-via-rsync@feature/add-symlink-and-sql-flows
.github/workflows/atomic-deploy.yaml:64
- This workflow references actions on a feature branch ref (
@feature/add-symlink-and-sql-flows). Once merged, that ref may not exist, breaking callers. Prefer a stable ref like@main(consistent with.github/workflows/synchronise.yaml).
- uses: pie/.github/actions/add-ssh-config@feature/add-symlink-and-sql-flows
.github/workflows/atomic-deploy.yaml:72
- This workflow references actions on a feature branch ref (
@feature/add-symlink-and-sql-flows). Once merged, that ref may not exist, breaking callers. Prefer a stable ref like@main(or a tag).
- uses: pie/.github/actions/swap-and-migrate@feature/add-symlink-and-sql-flows
.github/workflows/atomic-deploy.yaml:48
- This secret is described as defaulting to the
#uptime_alertsSlack channel, but the workflow actually hardcodes a Slack channel email address when the secret is unset (see thenotify-email:default below). Update the description so callers know they must provide an email address.
NOTIFY_EMAIL:
description: "Override recipient email (defaults to #uptime_alerts Slack channel)"
required: false
actions/swap-and-migrate/action.yml:173
- Failure notifications are only sent when
swap.shexits with code1or2. Many realistic failures (e.g. rsync/mv errors during component swap, missing release directory, wp-cli failures) will exit with other codes and send no notification even though the deploy failed.
- name: Fail the job
if: steps.swap.outcome == 'failure'
shell: bash
run: exit 1
.github/workflows/atomic-deploy.yaml:32
- The
releases-dirinput description says it defaults to areleasessibling ofwp-root, but the swap-and-migrate action (and README) default towp-root/releases(i.e., insidewp-root). This mismatch is likely to confuse callers.
This issue also appears on line 46 of the same file.
releases-dir:
type: string
description: "Absolute path to the releases directory on the server. Defaults to a 'releases' sibling of wp-root when not set."
required: false
default: ""
README.md:75
- Docs say
NOTIFY_EMAILdefaults to the#uptime_alertsSlack channel, but the implementation uses an email recipient (a Slack channel email address) and the action requires an email address forto:. Clarify this to avoid users passing a channel name that can't work with SMTP.
- `SSH_PRIVATE_KEY`: SSH private key. Required.
- `SMTP_SERVER`: SMTP server for failure notifications. Optional — set at organisation level.
- `SMTP_USERNAME`: SMTP username. Optional — set at organisation level.
- `SMTP_PASSWORD`: SMTP password. Optional — set at organisation level.
- `NOTIFY_EMAIL`: Override the notification recipient. Optional — defaults to `#uptime_alerts` Slack channel.
actions/swap-and-migrate/action.yml:78
scpuses the unescaped$MIGRATIONS_DIRin the remote destination path. Ifwp-root/releases-direver contains spaces or shell-special characters, the upload step will fail even though you already computed an escaped$MIGRATIONS_DIR_Qfor SSH commands.
This issue also appears on line 169 of the same file.
MIGRATIONS_DIR="$RELEASES_DIR/$GIT_SHA/migrations"
MIGRATIONS_DIR_Q=$(printf '%q' "$MIGRATIONS_DIR")
ssh server "mkdir -p $MIGRATIONS_DIR_Q"
scp "$GITHUB_ACTION_PATH/scripts/swap.sh" "server:$MIGRATIONS_DIR/swap.sh"
scp "$GITHUB_ACTION_PATH/scripts/migrate.sh" "server:$MIGRATIONS_DIR/migrate.sh"
When merging in we'll need the following to get notifications to work: