Skip to content

feat: migrate the community GitLab plugin's data (0.5.x) - #6

Merged
pikann merged 4 commits into
Paca-AI:masterfrom
gorlix:feat/migrate-community-plugin
Oct 29, 2026
Merged

pikann merged 4 commits into
Paca-AI:masterfrom
gorlix:feat/migrate-community-plugin

Conversation

@gorlix

@gorlix gorlix commented Oct 29, 2026

Copy link
Copy Markdown

Migrates the data of the community GitLab plugin (0.5.x) to Paca core, as discussed in Paca-AI/paca#649. Today an upgrade loses the project's token, its linked repositories and the task ↔ merge request / branch links. After this PR they come back as core Git data, the way the GitHub provider's 0002_git_provider.sql does it for its previous release.

What changes

Migration 0004_migrate_community_plugin.sql

  • Each project's access token becomes a project-scoped token connection in core (git_identities plus its git_webhook_endpoints row). Its repositories, merge requests, task links and task branches come with it:
    • external_id is GitLab's numeric project id;
    • the host and clone URL come from the instance URL.
  • The 0.5.x tables are in one of two places, and both are read:
    • 0.5.0 and 0.5.1 already used the id com.paca.gitlab, so their tables are in this plugin's own schema. 0002 used to drop them before anything could read them. It now keeps them, and 0004 drops them after carrying the rows over, as the GitHub provider does.
    • 0.5.2 used com.gorlix.gitlab, so its tables are in plugin_data_com_gorlix_gitlab. That schema is left untouched.
    • A project found in both is taken from 0.5.2, the newer.
  • It is idempotent (NOT EXISTS / ON CONFLICT) and does nothing when no 0.5.x schema exists. When core's tables are missing (a plugin-only database), only the tokens are copied.
  • Tokens are copied encrypted, as they are. Core hands every plugin the instance's ENCRYPTION_KEY, so they stay readable.
  • Changing 0002 is safe because no release of this plugin has shipped it yet.

Completion on first use (completeMigratedIdentity, mirrors completeMigratedToken in the GitHub provider)

  • The token's user is read from GitLab (GET /user) and a webhook secret is generated. Then the token's reach is classified, as for any unclassified identity.
  • Concurrent first uses keep whichever secret was stored first.
  • A token GitLab rejects returns connect GitLab again. A token the instance key cannot decrypt returns an error that names ENCRYPTION_KEY.

Old webhooks

  • 0.5.x registered one hook per repository, pointing at the old plugin's route. 0004 records them in gitlab_legacy_webhooks.
  • After EnsureWebhook has registered the new hook ("Try again" on the repository), each old hook is read. It is deleted only if its URL is still the old plugin's (…/api/v1/plugins/com.gorlix.gitlab/projects/…/webhook). A hook that was pointed elsewhere is left alone, and the plugin logs a warning.
  • This is best effort and never fails EnsureWebhook:
    • 404 and 403 count as done;
    • only retryable errors are tried again;
    • if the new hook cannot be registered, the old one stays.

README: "Upgrading from 0.5.x" is rewritten. It covers:

  • what is carried over and what is not;
  • the order of the steps: upgrade in place from 0.5.0/0.5.1, or from 0.5.2 install the new plugin before removing the old one;
  • what "Try again" does;
  • how to back up with pg_dump and how to roll back.

Testing

Checks. go vet, go test -race, golangci-lint (0 issues) and the tinygo wasip1 build pass.

Unit tests with the fake GitLab cover:

  • completion on first use, the generated secret verifying deliveries, a revoked token, concurrent completion, and a token the key cannot decrypt;
  • old hooks: deleted after the new hook, kept when they were repointed, kept when registration fails, already gone, and retried after a transient error.

Postgres. scripts/test-legacy-migration/run.sh runs the migrations the way core does on Postgres 16: one transaction per file, SET LOCAL search_path, and core's real 000073/000076 tables. It passes 65 of 65 checks:

  • the upgrade paths: from 0.5.2, in place from 0.5.0, and both together;
  • edge cases: a repository already linked by hand, an unknown MR state, a soft-deleted task, an instance URL with upper case, a port and a path, and an empty instance URL;
  • a rerun of 0004, a fresh install and a plugin-only database;
  • the store's own SQL for completing an identity and for finding its legacy hooks.

Real upgrade on a copy of a development instance. The instance ran 0.5.0 on an older core. Its restored copy was upgraded in an isolated stack on core master, with the GitHub provider installed too.

  • Core and both plugins migrated cleanly.
  • The project's connection, repository, merge request and three task branches show in Settings → Git and on the task.
  • The migrated token completed itself on first use against gitlab.com, and a merge request was read live through it.
  • Not exercised for real: "Try again" against the real GitLab project, since it would change that project's hooks. That part is covered by the unit tests.

Decisions

  • Old webhooks are removed automatically, but only after the repository's new hook is registered, and only when the hook still points at the old plugin (see above). The alternative was to leave them. GitLab would then keep failing deliveries to the removed route until it disables the hooks, and users would have to clean them up project by project.
  • The pre-upgrade backup is kept.
    • Before the real test, the development database was dumped with pg_dump -Fc and checked with pg_restore -l (432 TOC entries). Its SHA-256 and the restore command were noted next to it.
    • It is stored outside the repository with owner-only permissions, and it is never committed or uploaded.
    • Every test ran on a restored copy; nothing ever wrote to the original database.

Backup and rollback in production

The README now has these steps. In short, with the bundled Postgres of deploy/docker-compose.prod.yml:

# before upgrading
docker compose -f deploy/docker-compose.prod.yml exec -T postgres \
  pg_dump -U paca -d paca -Fc > paca-before-gitlab-0.6.dump
docker run --rm -v "$PWD":/d:ro postgres:16 pg_restore -l /d/paca-before-gitlab-0.6.dump | head

# to roll back: stop the API, restore, reinstall the previous plugin version
docker compose -f deploy/docker-compose.prod.yml stop api
docker compose -f deploy/docker-compose.prod.yml exec -T postgres dropdb -U paca --force paca
docker compose -f deploy/docker-compose.prod.yml exec -T postgres createdb -U paca paca
docker compose -f deploy/docker-compose.prod.yml exec -T postgres \
  pg_restore -U paca -d paca --no-owner < paca-before-gitlab-0.6.dump
docker compose -f deploy/docker-compose.prod.yml start api

With a managed database, use pg_dump "$DATABASE_URL" -Fc. The dump holds the encrypted tokens, so keep it as private as the database. When upgrading from 0.5.2, install com.paca.gitlab before removing com.gorlix.gitlab.

Known limits

  • A migrated connection shows GitLab access token with an empty handle in core. Core sets handle and display_name when a connection is created and never refreshes them, so they stay as the migration wrote them. The GitHub migration has the same limit. Reconnecting gives the full label.
  • Until "Try again", a migrated repository shows core's generic notice that events aren't reaching Paca, which mentions token permissions. The README tells users to press Try again.

🤖 Generated with Claude Code

gorlix and others added 4 commits October 29, 2026 11:10
Migration 0004 carries over what com.gorlix.gitlab 0.5.x kept in its own
schema: each project's access token becomes a project-scoped token
connection in core, with its repositories, merge requests, task links and
task branches. It mirrors the GitHub provider's migration and is a no-op
when the old schema or core's git tables are absent; rerunning it creates
nothing new. The old schema is left untouched.

A migrated identity is completed on first use: its user is asked of
GitLab, a webhook secret is generated, then its reach is classified. A
token GitLab rejects asks to connect again, and a token the instance key
cannot decrypt names ENCRYPTION_KEY.

The old plugin's webhooks are recorded and each is deleted once the
repository's new hook is registered, only if it still points at the old
plugin; failures are best effort and only retryable ones are tried again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Community releases 0.5.0 and 0.5.1 already used the id com.paca.gitlab,
so their tables are in this plugin's schema, and 0002 dropped them before
anything could carry them over. 0002 now keeps them; 0004 gathers the
rows of both releases (0.5.2's schema wins for a project found in both),
carries them over and then drops the tables in this schema, as the
GitHub provider does with its previous release's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rewrites "Upgrading from 0.5.x" for the migration: what is carried over
and what is not, the order of the steps for 0.5.0/0.5.1 (same id) and
0.5.2 (com.gorlix.gitlab), what "Try again" does to the old webhooks,
and how to back up the database first and roll back. The decryption
error now names the instance's ENCRYPTION_KEY, which core hands every
plugin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scripts/test-legacy-migration runs this plugin's migrations the way core
does (one transaction each, SET LOCAL search_path) on Postgres 16 with
core's git tables from a Paca checkout, and checks the upgrade from
0.5.2, the in-place upgrade from 0.5.0/0.5.1, both together, a rerun, a
fresh install and a plugin-only database, plus the store's own SQL for
completing an identity and finding its legacy hooks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gorlix added a commit to gorlix/paca that referenced this pull request Oct 29, 2026
Repositories listed for a project now carry webhook_attempted, true when
the audit log holds a webhook.register entry for them. One aggregated
query per listing, no new migration.

Refs Paca-AI#656. Repos carried over by the plugin migrations
(Paca-AI/paca-plugin-gitlab#6) arrive without a webhook and without
Paca ever having tried to register one.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
gorlix added a commit to gorlix/paca that referenced this pull request Oct 29, 2026
When webhook_attempted is false the notice no longer says Paca
couldn't register the webhook or suggests a permissions problem: it
explains that none was registered yet and offers "Register webhook".
After a failed try it falls back to the usual wording. New strings in
all nine locales.

Refs Paca-AI#656. Repos carried over by the plugin migrations
(Paca-AI/paca-plugin-gitlab#6) are in this state.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@pikann pikann left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Thank you for the contribution! 🚀

@pikann
pikann merged commit 3501ada into Paca-AI:master Oct 29, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants