Skip to content

pgAdmin 4 for UBI-9: SP1 node_modules tooling + SP2 local-npm-registry + SP3 Python 3.12 stack - #12

Open
rjd15372 wants to merge 51 commits into
mainfrom
pgadmin-sp1
Open

pgAdmin 4 for UBI-9: SP1 node_modules tooling + SP2 local-npm-registry + SP3 Python 3.12 stack#12
rjd15372 wants to merge 51 commits into
mainfrom
pgadmin-sp1

Conversation

@rjd15372

@rjd15372 rjd15372 commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR carries the pgAdmin 4 for UBI-9 effort sub-project by sub-project so every step is exercised by the OBS PR check before the whole lands. Design: docs/superpowers/specs/2026-08-26-pgadmin4-tooling-design.md (includes the decomposition into SP2 local-npm-registry, SP3 python3.12-* stack ported from openSUSE:Factory, SP4 percona-pgadmin4). Plan for SP1: docs/superpowers/plans/2026-08-26-pgadmin4-tooling.md.

  • SP1 — tooling: make percona-obs (and the obs-tools CI image) work with the OBS node_modules source service, which vendors npm dependencies for offline builds, and create the empty ppg:devel:pgadmin OBS project that will hold every pgAdmin package.
  • SP2 — local-npm-registry: the offline npm registry RPM that percona-pgadmin4 will BuildRequires at build time.
  • SP3 — Python 3.12 stack: the 71 python3.12-* RPMs of pgAdmin 4's dependency closure in ppg:devel:pgadmin, plus a shared build-backend stack and a dnspython bump in ppg:common:deps.

SP1 — Changes

  • tools/obs-services/npm_lockfile (+ .service, README) — repo-owned, stdlib-only OBS-style service: unpacks the obs_scm archive, runs npm install --package-lock-only in --subdir, validates lockfileVersion >= 2, writes package-lock.json to --outdir. Declared mode="manual" ahead of node_modules, so percona-obs generates the lockfile at sync time (pgAdmin ships only yarn.lock). Params: archive, subdir, npm-flags.
  • percona_obs/services.py.obscpio files produced by mode="manual" services (e.g. node_modules.obscpio) are kept for upload instead of being extracted/deleted; obs_scm archives are still extracted.
  • percona_obs/services.py / cmd_sync.py — the ignore_vendor special case (cargo_vendorvendor.tar.*) becomes a table-driven drift_tolerant_patterns() that also covers node_modules outputs (node_modules.obscpio, node_modules.spec.inc, *package-lock.json): compared by presence only in the --branch-from content check, so registry-driven byte drift never promotes a package by itself. cargo_vendor behaviour unchanged.
  • obs-tools imageobs-service-node-modules (+ python3-lxml), Node.js 22 + npm (NodeSource; matches the nodejs:22 module the packages build against), cpio, and the npm_lockfile service; build-obs-image.yml also triggers on tools/obs-services/**.
  • root/ppg/devel/pgadmin/project.yaml — new ppg:devel:pgadmin project, UBI_9 only (path list mirrors staging's UBI_9 entry) with ExpandFlags: module:nodejs:22. No packages yet; root/README.md documents the version-independent devel project.
  • Docsdocs/PERCONA_OBS_TOOL.md (prerequisites, "Vendoring npm dependencies" section, drift-tolerant table), .github/copilot-instructions.md, CLAUDE.md.

SP2 — Changes

  • root/common/deps/build/local-npm-registry/ — new RPM-only, noarch build-dep package ported from openSUSE devel:languages:javascript/local-npm-registry (upstream openSUSE/npm-localhost-proxy v1.1.0, the latest release). Source is the upstream release tarball via download_url; dist/ and node_modules/ (runtime dep ssri) are pre-bundled, so nothing is built. EL9 port of the spec: Requires: nodejs >= 20 + npm (the nodejs:22 module stream; EL has no npm-default), same /usr/bin/local-npm-registry node launcher, %license COPYING.
  • root/macros.yamlLOCAL_NPM_REGISTRY_VERSION: 1.1.0, following the pinned build-dep tool convention (bumps per "Bumping a Pinned Build-Dependency Tool Version" in PACKAGING_HOWTO.md).
  • UBI_9 only via package.yaml build: flags (pgAdmin 4 is a UBI-9-only product); root/README.md lists it under common/deps/build/.

SP3 — Changes

  • root/ppg/devel/pgadmin/python3-* (71 new) — pgAdmin 4 REL-9_9's Python 3.12 closure (cloud extras excluded), one EL spec template: PyPI sdist via download_url, pip wheel --no-build-isolation + pip install --root (legacy setup.py for 9 packages), import smoke test in %check, runtime deps doubled as BuildRequires. Build tools python3-cython 3.1.3, python3-poetry-core, python3-pdm-backend. bcrypt vendors its Rust crate with cargo_vendor.
  • root/ppg/common/deps/python3-dns bumped 1.15.0 → dnspython 2.8.0 (email-validator ≥ 2.0; name kept, python3-etcd/percona-patroni rebuilt by dep-cascade); new shared build-backend stack python3-flit-core 3.12.0, python3-packaging 25.0, python3-pathspec, python3-trove-classifiers, python3-hatchling 1.28.0 for EL8/EL9/UBI (RHEL's backends cannot build PEP 639 metadata; EL10/openSUSE use their distro ones via a spec conditional).
  • Version policy — reuse RHEL 9's python3.12-{cffi,cryptography 41,idna,pycparser,urllib3 1.26,setuptools 68} and common:deps' six/dateutil/psutil/click; pinned down where PEP 639 metadata would need setuptools ≥ 77 (keyring 25.2.1, jaraco.context 6.0.1, jaraco.functools 4.1.0, importlib-resources 6.5.2, Pillow 11.1.0), bidict 0.23.1, pyotp 2.9.0, ua-parser 0.18.0.
  • Docsdocs/PACKAGING_HOWTO.md "Python 3.12 packages (pyproject builds)"; root/README.md.
  • Design: docs/superpowers/specs/2026-08-26-pgadmin4-py312-stack-design.md.

Verification

SP1

  • pytest: 147 passed (23 new tests: service with a fake npm, _run_local_services with fake service binaries + real cpio archive, drift-tolerant patterns); black + pyright clean.
  • obs-tools image built locally; in-image check: node_modules, npm_lockfile, cpio, node v22.23.2, npm 10.9.8.
  • End-to-end on the real upstream: obs_scm (pgAdmin REL-9_9) → npm_lockfilenode_modules via sync push --dry-run (isv-pr profile), twice: 5-file upload set (node_modules.obscpio 197 MiB, node_modules.spec.inc 1 439 Source lines, package-lock.json lockfileVersion 3 / 1 550 packages, tarball, obsinfo), artifact set matches the drift-tolerant table exactly, second run fully cached. Details recorded in the spec's §10.
  • PR check run 32988024877 (head 30ce7a0, after the rebase onto main with percona-obs: never promote on content-check service failure; compare rendered referenced macros #13): synced to isv:percona:PR:pr-12 with 0 spurious promotes.

SP2

  • sync push --dry-run -P isv-pr common:deps:build local-npm-registry: download_url resolves; would-be upload is exactly local-npm-registry.spec + local_npm_registry-v1.1.0.tar.gz.
  • Rendered spec parses with rpmspeclocal-npm-registry-1.1.0-1.el9.noarch.
  • Acceptance: the PR check builds isv:percona:PR:pr-12:common:deps:build/local-npm-registry on UBI_9.

SP3

  • Dry-runs before pushing (sync push --dry-run -P isv-pr, all sync successful (dry run)): python3-hatchling, python3-trove-classifiers, python3-dns (common:deps); python3-bcrypt (download_url + cargo_vendor, 3 files), python3-psycopg-c, python3-flask, python3-wtforms, python3-paramiko, python3-typer, python3-rich.
  • OBS end state in isv:percona:PR:pr-12 (UBI_9 only, per the PR's UBI_9 label): ppg:devel:pgadmin 142/142 succeeded (71 packages × x86_64 + aarch64); ppg:common:deps 12/12 succeeded (python3-flit-core, -packaging, -pathspec, -trove-classifiers, -hatchling, -dns, × 2 arches). Board settled 2026-08-27 16:04 UTC.
  • Build-loop fixes — 12 packages failed the first build (9 on x86_64: backports-zstd, bcrypt, flask-principal, greenlet, gssapi, markupsafe, psycopg-c, qrcode, secretstorage; 3 only on aarch64: psycopg, ua-parser, wsproto); fixed by 10 commits over 3 rounds, all reviewed:
    • Round 1: 52d93bc all 77 specs — %check imported the source tree instead of the buildroot (missing native extensions, e.g. gssapi No module named gssapi.raw.creds) → %{__ospython} -P -c "import ...". 176d706 python3-{alembic,backports-zstd,greenlet,mako,markupsafe,secretstorage,wsproto} — RHEL setuptools 68 rejects PEP 639 license = "…" + license-files%prep sed to license = {text = "…"}, drop license-files (kept RHEL setuptools rather than shipping setuptools 80). 4441f17 python3-flask-principal — Flask/blinker missing from PyPI metadata → added as BuildRequires/Requires. 2b65469 python3-psycopg — pure-Python pq wrapper needs libpq at import → BuildRequires/Requires libpq. 3a385e0 python3-psycopg-c — psycopg_c refuses import unless psycopg is imported first → BuildRequires python3.12-psycopg, %check imports psycopg then psycopg_c. d8ade24 python3-qrcode — ambiguous #!/usr/bin/env python shebang in qrcode/console_scripts.py → removed in %prep. 9836542 python3-ua-parser — setup_requires=["pyyaml"] fetched from PyPI (offline build) → BuildRequires python3.12-pyyaml, drop setup_requires. fce0164 python3-bcrypt — cargo_vendor places vendor//.cargo/config.toml under src/_bcrypt/, not the sdist root → CARGO_HOME=$PWD/src/_bcrypt/.cargo.
    • Round 2: abdc615 python3-greenlet — wheel installs an unpackaged /usr/include/python3.12/greenlet/greenlet.h → added to %files.
    • Round 3: 5498b3e python3-wtforms — hatch build hook compiles translations with Babel (ModuleNotFoundError: babel during pip wheel) → BuildRequires python3.12-babel.
    • Operational: after the fixes, OBS rebuilt both PR projects three more times with _jobhistory reason "meta change" (download-on-demand path repos for Rocky 9 / EPEL 9 refreshing) on a slow x86_64 scheduler — roughly 4 hours from first push to a settled board; not caused by our pushes (every sync logged = project meta/config).
  • Open items (stated honestly, not resolved in this PR): the dnspython cascade (python3-etcd, percona-patroni) was not exercised in PR pgAdmin 4 for UBI-9: SP1 node_modules tooling + SP2 local-npm-registry + SP3 Python 3.12 stack #12 because it carries the no-dep-cascade label — the cascade rebuild happens on merge; and the ppg:common:deps packages were only built on UBI_9 in the PR — RockyLinux_8/9/10, UBI_8, and openSUSE builds happen after merge.

Notes / follow-ups

  • SP1 adds no packages; the rebuilt obs-tools image (with node_modules/npm) is only produced after merge (build-obs-image.yml triggers on main) and is needed before SP4's package lands.
  • sync push --project-only rejects a package-less project (targets.py), so ppg:devel:pgadmin is created on OBS when its first package syncs (SP3/SP4).
  • Found during SP1 verification, for SP4: npm does not use pgAdmin's yarn.lock as a resolution hint — the generated lockfile resolves newer caret-satisfying versions than upstream tested (4 of 5 sampled packages). SP4 has to decide between pinning and accepting the drift. The real ~200 MB upload timing check is also left to SP4's real package.
  • Deferred to SP4: chunked md5 / skip hashing drift-tolerant artifacts (cmd_sync.py, obs_api.py) once a ~200 MB artifact actually exists.

@rjd15372 rjd15372 added UBI_9 no-dep-cascade If used with `obs-sync*` labels, it prevents from packages that were not changed from being built obs-sync Trigger OBS sync push labels Aug 26, 2026
@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown

OBS Build Check — ❌ Sync failed

The OBS sync step failed. See the workflow run for details.

Updated: 2026-08-28 14:22 UTC

rjd15372 added a commit that referenced this pull request Aug 26, 2026
…rendered macros

Two --branch-from decision fixes (PR #12 CI analysis, 2026-08-26):

1. _content_matches_branch caught SystemExit from a failed local service
   run and returned False ("content differs"), so a transient obs_scm
   clone error (curl 56 Connection reset by peer) promoted an unrelated,
   unchanged package. A failed run says nothing about the content: the
   services are now retried once (_run_services_for_check) and a second
   failure aborts the whole sync with the service's error.

2. _macros_changed_since compared inherited macros.yaml at file level
   (git diff --name-only <sha>..HEAD). Commit 146694c moved macros
   between staging/17/extras, staging/17 and staging/macros.yaml with
   identical rendered values, which flagged every ppg/staging package as
   "inherited macros changed" and sent ~120 of them through the
   service-running content check on every PR run. The check now compares
   the rendered values of the %!{NAME} macros the package actually
   references (obs/, debian/, rpm/ files) between the synced SHA (via
   git show) and the working tree. load_macros is split into
   _macros_chain_files + resolve_macros so both trees resolve through
   the same parser. Against the legacy production SHA 6440e48 this drops
   ppg/staging from 315/315 flagged packages to 4, all genuine version
   bumps (pg_tde x3, 19/haproxy).

Tests: tests/test_content_check_retry.py, tests/test_macros_changed_since.py
(real temporary git repo, no git mocking).

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
@rjd15372 rjd15372 added obs-sync Trigger OBS sync push and removed obs-sync Trigger OBS sync push labels Aug 26, 2026
@rjd15372 rjd15372 changed the title percona-obs: support the OBS node_modules service (pgAdmin 4 tooling, SP1) pgAdmin 4 for UBI-9: SP1 node_modules tooling + SP2 local-npm-registry Aug 26, 2026
rjd15372 added a commit that referenced this pull request Aug 27, 2026
Design for the ~75 python3.12-* packages percona-pgadmin4 needs on UBI-9:
closure resolution, reuse policy (RHEL 9 + ppg:common:deps), dnspython
bump + shared hatchling build stack in ppg:common:deps, the shared EL
spec template (pyproject / setup.py / self-hosting families), inventory,
verification via PR #12, risks.

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
rjd15372 added a commit that referenced this pull request Aug 27, 2026
Six tasks: render the 77 python3-* package directories with a throwaway
scratchpad script (embedded as appendices with its data), commit them in
three bottom-up batches (ppg:common:deps backend stack + dnspython bump;
pgadmin build tools + leaves; dependents), document the Python 3.12 spec
template in PACKAGING_HOWTO, build everything in PR #12's OBS project
with a fix loop, and record outcomes in PR #12 and the spec.

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
@rjd15372 rjd15372 changed the title pgAdmin 4 for UBI-9: SP1 node_modules tooling + SP2 local-npm-registry pgAdmin 4 for UBI-9: SP1 node_modules tooling + SP2 local-npm-registry + SP3 Python 3.12 stack Aug 27, 2026
rjd15372 added a commit that referenced this pull request Aug 28, 2026
…-9_17)

Package layout and spec design for percona-pgadmin4 with -gunicorn (container
runtime, systemd unit), -httpd (mod_wsgi conf + setup helper) and -doc (rst
sources) subpackages; obs_scm(REL-9_17) → npm_lockfile → node_modules source
chain; config_distro.py with Percona defaults and PGADMIN_CONFIG_* env
overrides; the SP3 stack changes for 9.17 (libpass replaces passlib, certifi,
annotated-doc, gunicorn, bumps; Authlib kept at 1.6.12); verification via
PR #12 plus a container smoke test with the OBS-built RPMs.

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
rjd15372 added a commit that referenced this pull request Aug 28, 2026
Bake the repo-owned npm_lockfile OBS-style service (tools/obs-services/)
into ghcr.io/percona/obs-tools together with Node.js 22 + npm, cpio and
obs-service-node-modules, so percona-obs can generate package-lock.json
and vendor npm dependencies for packages whose upstream ships no npm
lockfile (pgAdmin 4). build-obs-image.yml now also rebuilds the image
when tools/obs-services/ changes.

Split out of the pgadmin-sp1 branch (PR #12): the PR check runs inside
obs-tools:latest, which is only rebuilt from main, so the service must
land on main before PR #12 can sync.

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
…ode_modules)

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
…ine errors

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
…rift-tolerant artifacts

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
… a package)

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
…re message

The cpio extraction path had no equivalent to the tarball path's
filter="data" (which rejects absolute paths and traversal), so a
crafted .obscpio could write outside the temp dir. Add GNU cpio's
--no-absolute-filenames to close that gap.

Also include the last 5 non-empty lines of npm's stdout in the
die() message on failure, so a failed npm install carries a real
diagnostic instead of just the exit code.

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
Drift tolerance only affects the --branch-from changed/unchanged
decision; it does not affect what a non-skipped sync uploads. Make
explicit that a regenerated drift-tolerant artifact (e.g. pgAdmin's
~220 MB node_modules.obscpio) still gets re-PUT and triggers a new
OBS revision/rebuild when the package is synced for any other
reason (or with --force); --skip-unchanged avoids this for
tag-pinned packages, and --no-cache is the deliberate refresh.

Also note that each new upstream commit of an npm-vendored package
adds a .cache/services entry of roughly the cpio's size, bounded
locally by prune_cache (7-day unused eviction) and in CI by
actions/cache size limits.

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
…ring and friends

The 35 remaining libraries of pgAdmin 4's Python 3.12 closure, which
depend on part 1 / ppg:common:deps packages: Flask 3.1.3 and its
extensions (Flask-Security-Too 5.6.2, Flask-SocketIO, Flask-Migrate, ...),
SQLAlchemy 2.0.52 + alembic, Werkzeug/Jinja2, keyring 25.2.1 stack,
paramiko/sshtunnel, psycopg 3.2.10, typer/rich, gssapi 1.10.1 (Cython 3),
python-socketio/engineio, WTForms.

Runtime dependencies are also BuildRequires so the %check import smoke
test runs against the buildroot; OBS orders the builds accordingly.

Design: docs/superpowers/specs/2026-08-26-pgadmin4-py312-stack-design.md
Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
…duplicated by the preamble

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
…dmin Python stack

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
… the source tree, is imported

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
…e,wsproto}: patch PEP 639 licence fields for setuptools 68

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
…rom PyPI metadata)

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
…port)

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
…s.py

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
…fetching it

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
…bcrypt/Cargo.toml

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
…tch build hook

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
…thon RPM name

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
…-only closure edges)

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
…uires exception, aarch64 wording

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
…-9_17)

Package layout and spec design for percona-pgadmin4 with -gunicorn (container
runtime, systemd unit), -httpd (mod_wsgi conf + setup helper) and -doc (rst
sources) subpackages; obs_scm(REL-9_17) → npm_lockfile → node_modules source
chain; config_distro.py with Percona defaults and PGADMIN_CONFIG_* env
overrides; the SP3 stack changes for 9.17 (libpass replaces passlib, certifi,
annotated-doc, gunicorn, bumps; Authlib kept at 1.6.12); verification via
PR #12 plus a container smoke test with the OBS-built RPMs.

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
…OBS loop, container smoke test)

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
…uires (pre-flight ruling)

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
Bump Flask-Security-Too 5.8.2, Flask-SocketIO 5.6.1, Flask-WTF 1.3.0,
gssapi 1.11.1 (Cython 3.2.4), psycopg/psycopg-c 3.3.4, pytz 2026.3.post1,
typer 0.26.8. Add annotated-doc 0.0.5, certifi 2026.6.17, libpass 1.9.3
(replaces passlib) and gunicorn 26.2.0 (container runtime). Drop passlib
and importlib-resources. Aggregate the common:deps runtime packages
(click, six, dateutil, psutil, dns) so the project repo is self-contained.

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
…s, certifi consumer

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
Ported from openSUSE's pgadmin4.spec to EL9/python3.12. Sources come
from the upstream REL-9_17 tag through obs_scm -> npm_lockfile ->
node_modules; webpack runs against local-npm-registry. Subpackages:
-gunicorn (launcher + systemd unit, PGADMIN_CONFIG_* env mapping; the
container runtime), -httpd (mod_wsgi conf + percona-pgadmin4-setup-web),
-doc (rst sources; Help menu links to the online manual).

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
…aging, console scripts, httpd conf, timeout, typing-extensions

Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
Signed-off-by: Ricardo Dias <ricardo.dias@percona.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-dep-cascade If used with `obs-sync*` labels, it prevents from packages that were not changed from being built obs-sync Trigger OBS sync push UBI_9

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant